Le temps [Frames, horloges et décomptes]

Le temps est une notion essentielle dans un programme. Cette notion permet de connaitre le temps d’exécution d’un programme, à quelle vitesse ce dernier s’exécute, de découper des animations selon un certains timing mais aussi de prévoir des animations ou actions à des instants précis.

Nous verrons ici trois méthodes permettant d’appréhender le temps dans un programme : les frames, les horloges et le décompte.

Les frames

Les frames (ou images) sont directement inspiré du cinéma et de l’animation, ce sont les images affichées par secondes. En programmation une frame est une exécution complète de la boucle draw(), ainsi le nombre total de frames correspond au nombre total de fois que boucle draw() s’est effectuée. Les frames par seconde (ou FPS) permettent de connaitre la vitesse d’exécution du programme.

Processing permet d’obtenir rapidement le nombre total de frames effectuées ainsi que le nombre de frames par seconde à l’aide des méthodes suivantes :

float nbTotalDeFrame = frameCount;
float nbDeFrameParSeconde = frameRate;

Sachant que notre programme s’exécute à une vitesse moyenne de 60 frames par seconde nous pouvons donc obtenir le nombre de secondes durant lequel le programme s’est exécuté par un calcul simple :

Temps = frameCount/frameRate

Cependant cette méthode n’est pas conseillée compte tenu du fait qu’elle ne nous renverra jamais un temps réel.
En effet, au cours de son exécution le nombre de frames par seconde d’un programme peut grandement varier en fonction du nombre de calculs à effectuer. Nous pouvons d’ailleurs remarquer que ce dernier oscille entre des valeurs décimales comprises entre 58 et 60. Cette méthode n’est donc pas la plus juste lorsque nous souhaitons calculer un temps précis.

Dans l’exemple ci-dessous nous dessinons une ligne d’un bout à l’autre de la scène en 600 frames. Lorsque le programme atteint la frame 600, nous arrêtons la double draw() à l’aide de la méthode noLoop().

int maxFrame = 600; //Limit de frame
float maxWidth; //Taille Max de la ligne
void setup()
{
  size(800, 600, P2D);
}
void draw()
{
  background(0);
  if (frameCount <= maxFrame)
  {
    maxWidth = map(frameCount, 0, maxFrame, 0, width);
  }
  else
  {
    noLoop();
  }
  stroke(255);
  line(0, height/2, maxWidth, height/2);

  println(«nombre total de frame : «+frameCount);
  println(«FrameRate : «+frameRate);
}

ixd_letemps_00

Les horloges

Une seconde méthode permettant de gérer du temps durant un programme consiste à se référer à l’horloge de la machine sur laquelle se dernier s’exécute. Cela permet, quelques soit la vitesse d’exécution du programme, de toujours obtenir une valeur de temps universelle et juste.

Processing nous permet de questionner l’horloge de manière très simple à l’aide des méthodes suivantes :

hour() // renvoie des valeurs entre 0 - 23.
minute() // renvoie des valeurs entre 0 - 59.
second() // renvoie des valeurs entre 0 - 59.
millis() 
day() // renvoie des valeurs entre 1 - 31.
month() // renvoie des valeurs entre 1 - 12.
year()

Nous noterons que les méthodes hour(), minute() et second() renvoi les valeur actuelles de l’horloge alors que la méthode millis() renvoi le temps d’exécution du programme en millisecondes.

Il est donc très facile, à partir de ces méthodes, de réaliser une horloge digitale.

int sWidth = 800;
int sHeight = 400;

void setup()
{
  size(sWidth, sHeight, P2D);
}

void draw()
{
  background(0);
  textSize(30);
  
  textAlign(RIGHT);
  text(hour()+» : «+minute()+» : «+second()+» : «+millis(), width/2-20, height/2);
  
  textAlign(LEFT);
  text(day()+» / «+month()+» / «+year(), width/2+20, height/2);
}

ixd_letemps_01

Le décompte ou timer

Le décompte ou timer est une des méthodes les plus rependues permettant de gérer du temps lors de l’exécution d’un programme. Cette méthode permet de lancer un décompte en millisecondes. Pour cela nous utiliserons une class Timer. Cette classe permet de calculer une temps passé ainsi qu’un temps restant. Elle se construit de la manière suivante :

class Timer {

  int savedTime;
  int totalTime;
  int passedTime;
  int remainingTime;
  boolean timerStarted, timerStopped;

  Timer(int tempTotalTime) {
    totalTime = tempTotalTime;
    timerStarted = false;
    timerStopped = true;
  }

  void start() {
    if (timerStopped) {
      savedTime = millis();
      timerStarted = true;
      timerStopped = false;
    }
  }

  void stop() {
    timerStopped = true;
    timerStarted = false;
  }

  void reset() {
    savedTime = millis();
  }

  boolean isFinished() {
    if (timerStarted) {
      passedTime = millis()- savedTime;
      if (passedTime > totalTime) {
        timerStarted = false;
        timerStopped = true;
        return true;
      } 
      else {
        return false;
      }
    } 
    else {
      return true;
    }
  }

  int getRemainingTime() {
    if (timerStarted) {
      remainingTime = totalTime-passedTime;    
      return remainingTime;
    } 
    else {
      return -1;
    }
  }
}

Les méthodes start(), stop() et reset() permettent respectivement de lancer arrêter ou réinitialiser le timer.
Les méthodes isFinished() et getRemainingTime() permettent quant à elles de savoir si le timer est fini et quelle est son temps passé en millisecondes.

Cette classe utilise un algorythme simpme permettant de décompter le temps.
Lorsque ce dernier est lancé, le timer sauvegarde le temps d’exécution actuel du programme à l’aide de la méthode millis() dans une variable correspondant au point de départ de notre Timer.
Enfin nous effectuons le calcul suivant :

TempsPassé = TempsactuelleEnMillisecondes – TempsDeDepart

Enfin si cette valeur est supérieur au temps total que nous souhaitons décompter alors notre timer est terminé.

Ainsi, pour créer un timer à l’aide de cet objet il nous suffira alors de créer un objet de type Timer, de le déclarer avec le temps que nous souhaitons décompter puis de le lancer à l’aide de la méthode start(). Nous pourrons ensuite, lors de l’exécution de la boucle draw() interroger l’état de notre timer (en cours ou fini) ainsi que son temps restant.

int sWidth = 800;
int sHeight = 400;

Timer monTimer;
float taille;

void setup()
{
  size(sWidth, sHeight, P2D);
  monTimer = new Timer(5000);
  monTimer.start();
}

void draw()
{
  background(0);

  if (monTimer.isFinished())
  {
    monTimer.start();
  }

  taille = map(monTimer.getRemainingTime(), 0, monTimer.totalTime, 100, 300);
  
  noFill();
  stroke(255);
  ellipse(width/2, height/2, taille, taille);
  
  fill(255);
  textAlign(CENTER);
  text(monTimer.getRemainingTime(), width/2, height/2);

  println(monTimer.getRemainingTime());
}

class Timer {

  int savedTime;
  int totalTime;
  int passedTime;
  int remainingTime;
  boolean timerStarted, timerStopped;

  Timer(int tempTotalTime) {
    totalTime = tempTotalTime;
    timerStarted = false;
    timerStopped = true;
  }

  void start() {
    if (timerStopped) {
      savedTime = millis();
      timerStarted = true;
      timerStopped = false;
    }
  }

  void stop() {
    timerStopped = true;
    timerStarted = false;
  }

  void reset() {
    savedTime = millis();
  }

  boolean isFinished() {
    if (timerStarted) {
      passedTime = millis()- savedTime;
      if (passedTime > totalTime) {
        timerStarted = false;
        timerStopped = true;
        return true;
      } 
      else {
        return false;
      }
    } 
    else {
      return true;
    }
  }

  int getRemainingTime() {
    if (timerStarted) {
      remainingTime = totalTime-passedTime;    
      return remainingTime;
    } 
    else {
      return -1;
    }
  }
}

ixd_letemps_02

Un exemple d’horloge décomptant les secondes

Dans cet exemple nous allons réaliser une horloge décomptant le temps d’exécution du programme en minutes et secondes. Pour cela nous allons utiliser le concept simple du cadran de montre en secondes. À chaque minute passée nous dessinerons un nouveau cadran plus grand que le cadran précédent, chaque cadran se composant de cercles représentant les 60 secondes passées.

horloge

Pour cela nous aurons besoins de créer différentes classes : une classe cadran, que nous appellerons secondsDial, et une classe point qui permettra de dessiner un cercle par seconde sur notre cadran.

La classe point sera relativement simple et possédera peu de méthodes. Nous aurons besoins de connaitre une position x et y qui seront définies dans une méthode de la classe secondsDial. Nous définirons un taille ainsi qu’une couleur, enfin nous définirons une méthode display() qui dessinera nos points.

class Point
{
  //variables
  float x;
  float y;
  float taille;
  float rgb;
  
  Point(float x_, float y_)
  {
    x = x_;
    y = y_;
    taille = 10;
    rgb = 0;
  }
  
  void run()
  {
    display();
  }
  
  void display()
  {
    fill(127);
    noStroke();
    ellipse(x, y, taille, taille);
  }
}

Notre classe cadran sera quant à elle un peu plus difficile à mettre en place. Nous aurons besoins des variables suivantes :

  • La position x,y du cadran
  • Le rayon du cadran
  • Un ArrayList d’objet Point (nos cercles)
  • Un compteur de cycle afin de savoir si notre cadran à atteint 60 secondes
  • Un décompte des secondes
  • Une limite de secondes

Afin de réaliser une horloge décomptant le temps d’exécution du programme nous utiliserons la méthode millis(). Celle-ci nous permettra de connaitre en millisecondes le temps d’exécution du programme alors que la méthode seconde() nous renvoi la valeur seconde de notre horloge machine (cf partie 03). Les milliscondes s’incrémentant nous aurons donc besoins de fixer une limite afin de savoir quand nous aurons atteint les 60 secondes pour chaque cadran. C’est la raison pour laquelle nous avons besoins ici d’une variable secondesLimites qui sera équale au temps actuel d’exécution du programme en millisecondes + 60000 millisecondes (soit 1 minute).

NB : Nous pourrions utiliser un timer afin de réaliser ces calculs mais nous verrons ici comment les réaliser « from scratch » afin de se familliariser avec la méthode millis() et le décompte d’un temps

Notre constructeur sera alors :

class secondsDial
{
  //variables
  float posX, posY;
  float radius;
  ArrayList<Point> points;
  int countCycle;
  int secondes;
  int secondesLimite;

  //constructeur
  secondsDial(float radius_, float posX_, float posY_)
  {
    posX = posX_;
    posY = posY_;
    radius = radius_;
    countCycle = 0;
    secondes = 0;
    secondesLimite = millis()+60000;

    points = new ArrayList<Point>();
    
  }
}

Afin de décompter les secondes de notre cadran nous réaliserons une méthodes updateSecondes nous permettant de mapper la valeur millis() en secondes pour notre cadran.

void updateSeconde()
  {
    if(millis()<=secondesLimite)
    {
      secondes = int(map(millis(), secondesLimite-60000, secondesLimite, 0, 60));
    }
    else
    {
      secondes = 60;
    }
  }

Nous réaliserons ensuite une méthodes booléenne nous permettant de savoir si notre cadran a atteint sa limite de décompte (60 secondes)

 boolean cycle()
  {
    if (countCycle == 0)
    {
      if (secondes >= 60)
      {
        countCycle = 1;
        return true;
      }
      else
      {
        return false;
      }
    }
    else
    {
      return true;
    }
  }

Une utiliserons ensuite une méthode update() afin d’ajouter un objet point à notre tableau dynamique à chaque seconde passée. Afin de définir la position de nos point sur notre cadran nous utiliserons les base de trigonométrie nous permettant de définir la position d’un point sur un cercle (cf cours trigonométrie)

  void update()
  {
    if (!cycle())
    {

      float theta = map(secondes, 0, 60, 0, 360);
      float x = radius*cos(radians(theta))+posX;
      float y = radius*sin(radians(theta))+posY;

      if (points.size()-1 != secondes)
      {
        points.add(new Point(x, y));
      }
    }
  }

Enfin nous réaliserons une méthode run regroupant nos mises à jour et notre dessin.

 void run()
  {
    updateSeconde();
    update();
    display();
  }

Une fois notre objet cadran réalisé il ne nous restera plus qu’à l’appeler dans notre boucle draw(). Sachant que nous souhaitons ajouter un cadran de plus en plus grand à chaque minutes nous aurons besoins de créer une tableau dynamique d’objet secondsDials. Enfin à l’aide d’une condition nous interrogerons le dernier cadran afin de savoir si son cycle est terminé, et ce à l’aide de sa méthode booléenne cycle(). Si ce dernier est fini nous ajouterons alors un nouveau cadran dont le rayon sera défini par le rayon du cadran inférieur + 10 pixels.
Enfin il ne faudra pas oublier d’afficher nos cadrans à l’aide de leur méthodes run()

int sWidth = 700;
int sHeight = sWidth/2;

ArrayList<secondsDial> mySD;
int lastElement;

void setup()
{
  size(sWidth, sHeight, P2D);
  smooth(8);

  mySD = new ArrayList<secondsDial>();
  mySD.add(new secondsDial(50, width/2, height/2));
}

void draw()
{
  background(255);
  
 
  for (int i =0; i < mySD.size(); i++)
  {
    secondsDial sd = mySD.get(i);
    sd.run();
    
  }
  
  
  if(mySD.get(lastElement).cycle())
  {
    //println("newElements");
    float newRadius = mySD.get(lastElement).radius+10;
    //println(mySD.get(lastElement).points.size());
    mySD.add(new secondsDial(newRadius, width/2, height/2));
    lastElement = mySD.size()-1;
  }
}

ixd_letemps_03

Grilles et répétitions de motifs (Structures itératives & conditionnelles)

On appelle structure iterative une structure qui permet de répéter un certain nombre de fois une série d’instructions simples ou composées. Celle-ci permet notamment d’effectuer une grand nombre d’actions ou de calculs simultanés. On appelle structure conditonnelle une structure permettent de tester si une condition est vraie ou non. Cette structure est souvent utilisée afin d’attibuer une valeur à une variable booléenne.

Les structures itératives et conditionnelles sont deux grande bases de la programmation. Ici nous verrons comment les utiliser afin de créer une série de motifs génératifs.

Structure itérative : construction

La structure itérative permet d’executer plusieurs fois une ou des instructions. Elle peut s’écrire de plusieurs manières différentes. Ici nous nous concenterons sur la plus connue : La boucle for(). Celle-ci s’écrit de la manière suivante :

for(int i=0; i<Maximum; i++)
{
instruction
}

Où i est le compteur; i est inferieur au nombre maximum de répétitions et i s’incrémente de 1. On peut alors traduire cette phrase de la manière suivante : Pour i = 0; i étant toujours inférieur à une valeur maximum
et i s’incrémentant de 1 à chaque boucle alors…
Appliquons cela à un exemple concret. Ici je souhaite déssiner un motif composé de cercles de diamètre 30 pixels et se répétant tout les 40 pixels.

void setup()
{
  size(500, 200, P2D);
  smooth(8);
}

void draw()
{
  background(255);

  for (int i=0; i<width; i+=40)
  {

      ellipse(i+15, height/2, 30, 30);

  }
}

ixd_grille_00

On peut alors traduire cette phrase de la manière suivante : Pour i=0; i étant toujours inférieur à la largeur de mon sketch; et i incrémentant de 40 pixels (0, 40, 80…) je dessine des cercles dont x = i, y = moitié de la hauteur de mon skecth et de diamètre 30 pixels.

Dans notre premier exemple nous avons utilisé une structure itérative pour répéter notre visuel sur l’axe de x. Dans le cadre d’un motif nous aurons besoins de la répéter à la fois sur les x et les y. Pour cela nous allons utiliser une double boucle for.

void setup()
{
  size(500, 200, P2D);
  smooth(8);
}

void draw()
{
  background(255);
  
  noFill();
  stroke(0);
  for (int i=0; i<width; i+=40)
  {
    for (int j=0; j<height; j+=40)
    {
      ellipse(i+15, j+15, 30, 30);
    }
  }
}

ixd_grille_01

Structure conditionnelle : construction

La structure conditionnelle est la structure la plus basique en programmation. Elle permet d’exécuter une instruction
si un condition est validée. Il existe plusieurs structures conditionnelles :

  • If (si…)
  • If else (si… sinon…)
  • If else if (si… sinon si…)
  • switch (Dans le cas où …)

Dans un premier temps nous nous concentrerons sur les 3 premières structures. Reprenons notre première itération et
ajoutons la condition suivante : Si i est supérieur à la moitié de la largeur de la scène alors
mes cercles sont bleu. Il s’agit ici d’une condition simple if(). Elle se traduira par :

void setup()
{
  size(500, 200, P2D);
  smooth(8);
}

void draw()
{
  background(255);
  fill(255);

  for (int i=0; i<width; i+=40)
  {
    if (i > width/2)
    {
      fill(0, 0, 255);
    }
    ellipse(i+15, height/2, 30, 30);
  }
}

ixd_grille_02

Ajoutons maintenant une second condition à notre phrase : Si i est supérieur à la moitié de la largeur de la scène alors mes cercles sont bleu, sinon, il sont rouge. Il s’agit ici d’une condition if()…else{}. Elle se traduira de la manière suivante :

void setup()
{
  size(500, 200, P2D);
  smooth(8);
}

void draw()
{
  background(255);
  fill(255);

  for (int i=0; i<width; i+=40)
  {
    if (i > width/2)
    {
      fill(0, 0, 255);
    }
    else
    {
      fill(255, 0, 0);
    }
    ellipse(i+15, height/2, 30, 30);
  }
}

ixd_grille_03

Enfin complexifions un peu plus notre phrase de la manière suivante : Si i est supérieur à 1/3 de la largeur de la scène alors mes cercles sont vert, sinon si i est supérieur a 2/3 de la largeur de la scène alors mes cercles sont bleu, sinon il sont rouge. Il s’agit ici d’une condition multiple if()…else if(). Elle se traduira
de la manière suivante :

void setup()
{
  size(500, 200, P2D);
  smooth(8);
}

void draw()
{
  background(255);
  fill(255);

  for (int i=0; i<width; i+=40)
  {
    if (i > width/3*2)
    {
      fill(0, 0, 255);
    }
    else if (i > width/3)
    {
      fill(0, 255, 0);
    }
    else
    {
      fill(255, 0, 0);
    }
    ellipse(i+15, height/2, 30, 30);
  }
}

ixd_grille_04

Il est est possible de définir plusieurs conditionnelles de type OU ou ET à l’aide des opérateurs logique || et &&.
Ainsi nous pourrions dire : Si la position Y de la souris est supérieur à la moitié de la hateur de la scène ET que i est paire alors nos cercles sont bleu, sinon il sont rouge. Cela se traduirai par l’utilisation de l’opérateur logique && dans notre condition.

NB : Pour savoir si i est paire ou impaire nous utiliserons le modulo %. Cette opérateur
permet de connaitre la valeur résiduelle d’une division, c’est à dire son reste. Or si on
divise un nombre paire par 2 sont reste nulle. Cela se traduira en code par if(i%2 == 0).

void setup()
{
  size(500, 200, P2D);
  smooth(8);
}

void draw()
{
  background(255);
  fill(255);
  
  for(int i=0; i<width; i++)
  {
    float modulo = i%2;
    if(modulo > 0 && i<(width/2))
    {
      fill(0,0,255);
    }
    else
    {
      fill(255,0,0);
    }
    ellipse(i*40, height/2, 30, 30);
  }
}

ixd_grille_05

De la même manière que la double condition (&&) il est possible de réaliser une condition OU à l’aide de ||.
Ainsi nous pourrions dire : Si la position Y de la souris est supérieur à la moitié de la hateur de la scène OU que i est paire alors nos cercles sont bleu, sinon il sont rouge. Cela se traduirai par l’utilisation de l’opérateur logique || dans notre condition.

void setup()
{
  size(500, 200, P2D);
  smooth(8);
}

void draw()
{
  background(255);
  
  
   for(int i=0; i<width; i++)
  {
    if(mouseY > height/2 || i%2 ==0)
    {
      fill(0,0,255);
    }
    else
    {
      fill(255, 0, 0);
    }
    ellipse(i*40, height/2, 30, 30);
  }
}

ixd_grille_06

La dernière structure conditionnelle est la structure switch. Son fonctionnement est très proche de la structure if()…else if() mais celle-ci est plus pratique dans le cas où nous avons deux ou trois cas/condition à vérifier. Celle-ci fonctionne de la manière suivante :

int num = 1;
switch(num) {
case 0:
println(«Zero»); // Does not execute
break;
case 1:
println(«One»); // Prints «One»
break;
}

Elle se traduit de la manière suivante : Pour la variable entière i. Si celle ci est égale à 0 alors on effectue l’action println(«Zero»), si elle est égale à 1 alors on effectue l’action println(«Un»).

Prenons la en main de la manière suivante : Pour une variable entière «pattern», si celle-ci est égale à 0 alors mes cercle seront rouge, si celle-ci est égale à 1 alors mes cercles seront vert, enfin, si celle-ci est égale à 2 alors mes cercles seront bleu.

NB : Ici nous utiliserons de nouveau le modulo. En effet dans le cas d’une boucle de nombreux calculs à l’aide de modulo permettent de créer des suites.Ici nous définirons pattern à l’aide de i%3 = 0,1,2,0,1,2,0,1,2…

void setup()
{
  size(500, 200, P2D);
  smooth(8);
}

void draw()
{
  background(255);


  for (int i=0; i<width; i++)
  {
    int pattern = i%3;
    switch (pattern)
    {
    case 0:
      fill(255, 0, 0);
      break;
    case 1:
      fill(0, 255, 0);
      break;
    case 2:
      fill(0, 0, 255);
      break;
    }
    ellipse(i*40, height/2, 30, 30);
  }
}

ixd_grille_07

Un exemple de motif utilisant les structures conditionnelles et itératives

Dans cette exemple nous verrons comment utiliser les itérations afin de réaliser une motif composé de triangles.

motif_1_00111_2

Nous allons dans une premier temps définir la composition de notre motif. Ici nous souhaitons obtenir un motif à l’aide de superposition de triangles de teinte proche et ayant suffisamment de transparence pour laisser apparaitre leurs superpositions. Nous aurons aussi besoins de créer une grille à l’aide d’une double boucle for et d’en définir la résolution. Enfin nous utiliserons la méthode colorMode() pour travailler en teinte saturation et luminosité.

info

Dans un premier temps nous allons devoir définir les variables dont nous aurons besoins :

  • – Position x y des sommest A, B, C, D
  • – Teinte et incrément de teinte pour ne pas uniformiser les couleurs
  • – Résolution de la grille
  • – Random Seed (permettant de définir une constante à une méthode aléatoire)
int sWidth = 400;
int sHeight = sWidth*2;
int xA, yA, xB, yB, xC, yC, xD, yD;
float hue;
int Spacing=20;
int colorInc = 20;
int actualRandomSeed = 1000;

Nous définissons ensuite le constructeur de notre skecth

void setup()
{
size(sWidth, sHeight, P2D);
colorMode(HSB, 360, 100, 100, 100);
}

Enfin nous définissons notre boucle draw(). Nous utiliserons la méthode randomSeed() afin de définir une constante pour tout les nombre aléatoire que nous générerons. Nous créons ensuite notre double boucle for afin d’établir notre grille de motif de résolution «Spacing».

void draw()
{
background(0, 0, 100);
randomSeed(actualRandomSeed);

for (int gridX = 0; gridX<width/Spacing; gridX ++)
{
for (int gridY = 0; gridY<height/Spacing; gridY ++)
{
}
}
}

Enfin dans notre double boucle for() nous définissons les position de nos sommets où :

  • xA = gridX*Spacing;
  • yA = gridY*Spacing;
  • xB = xA+Spacing;
  • yB = yA;
  • xC = xA;
  • yC = yA+Spacing;
  • xD = xA+Spacing;
  • yD = yA+Spacing;

Afin de créer un dégradé de couleur nous utiliserons la méthode map() afin de définir la teinte gloable de nos triangles en fonction de leur position sur l’axe Y. La méthode map() permet de réaliser une règle de trois afin de mapper une valeur d’un rang de valeurs à un autre rang de valeurs.

xA = gridX*Spacing;
yA = gridY*Spacing;
xB = xA+Spacing;
yB = yA;
xC = xA;
yC = yA+Spacing;
xD = xA+Spacing;
yD = yA+Spacing;
hue = map(yA, 0, height, 180, 230);

Enfin, notre dernière étape consistera à dessiner nos triangles. Afin d’obtenir des triangles de teinte et d’opacité différentes nous utiliserons la méthode random() afin de celle-ci soit défini de manière aléatoire.

noStroke();
//ABC
fill(random(hue-colorInc, hue+colorInc), random(50, 100),
random(50, 100), random(20, 100));
triangle(xA, yA, xB, yB, xC, yC);
//BCD
fill(random(hue-colorInc, hue+colorInc), random(50, 100),
random(50, 100), random(20, 100));
triangle(xB, yB, xC, yC, xD, yD);
//ABD
fill(random(hue-colorInc, hue+colorInc), random(50, 100),
random(50, 100), random(20, 100));
triangle(xA, yA, xB, yB, xD, yD);
//ADC
fill(random(hue-colorInc, hue+colorInc), random(50, 100),
random(50, 100), random(20, 100));
triangle(xA, yA, xD, yD, xC, yC);

Ce qui nous donnera au final

int sWidth = 400;
int sHeight = 400;

int xA, yA, xB, yB, xC, yC, xD, yD;
float hue;
int Spacing=20;
int colorInc = 20;
int actualRandomSeed = 1000;

void setup()
{
  size(sWidth, sHeight, P2D);

  colorMode(HSB, 360, 100, 100, 100);
}



void draw()
{
  background(0, 0, 100);
  randomSeed(actualRandomSeed);

  for (int gridX = 0; gridX<width/Spacing; gridX ++)
  {
    for (int gridY = 0; gridY<height/Spacing; gridY ++)
    {
      xA = gridX*Spacing;
      yA = gridY*Spacing;

      xB = xA+Spacing;
      yB = yA;

      xC = xA;
      yC = yA+Spacing;

      xD = xA+Spacing;
      yD = yA+Spacing;


      hue = map(yA, 0, height, 180, 230);

      noStroke();

      //ABC
      fill(random(hue-colorInc, hue+colorInc), random(50, 100), random(50, 100), random(20, 100));
      triangle(xA, yA, xB, yB, xC, yC);

      //BCD
      fill(random(hue-colorInc, hue+colorInc), random(50, 100), random(50, 100), random(20, 100));
      triangle(xB, yB, xC, yC, xD, yD);

      //ABD
      fill(random(hue-colorInc, hue+colorInc), random(50, 100), random(50, 100), random(20, 100));
      triangle(xA, yA, xB, yB, xD, yD);

      //ADC
      fill(random(hue-colorInc, hue+colorInc), random(50, 100), random(50, 100), random(20, 100));
      triangle(xA, yA, xD, yD, xC, yC);
    }
  }
}

void mousePressed()
{
  actualRandomSeed = int(random(10000));
}

ixd_grille_08

Ressources et Aide en ligne sur Processing

Il n’est pas rare d’être confronté à un problème qu’on n’arrive pas à résoudre lors du développement d’un projet. Il est alors très utile de s’apercevoir qu’il existe des communautés prêtes à nous aider. Voici une liste non-exhaustive de site est forum où vous pourrez trouver de l’aide.

Les forums

Forum officiel processing
CodeLab

Les cours en ligne (en dehors de eartinteractif)

processing.org
Free Art Bureau (Je vous le conseil aussi comme ressource de veille)
tutoProcessing
École d’Aix – Processing
Cours de Jean-Noël Lafargue
Learning Processing (Daniel Shiffman)

Les cours vidéos

Plethora
Daniel Shiffman ITP beginner
Daniel Shiffman Nature of Code

Exemples en ligne

openprocesing

Lieux de recherche

Liste des lieux et adresses pour la recherche de la mémoire

 

Bibliothèques parisiennes, materiothèques

Nom Domaines, spécialités Adresse Remarque
Bibliothèque du Centre Pompidou

 

presse, parlés, films, musiques, photos, cédéroms, bases de données, DVD… 01 44 78 12 75

BPI

 ouvert jusqu’à 22h
BnF, François Mitterrand à Tolbiac Il y a tout 75013 Paris M° Quai de la Gare / Bibliothèque Mitterrand
01 53 79 59 5
 accès restreint
bibliothèque des Arts Décoratifs design et arts décos 111 rue de Rivoli
PARIS 75001
01 44 55 59 36
www.lesartsdecoratifs.fr/
bibliothèque de la Cité de l’architecture et du patrimoine dédiée à l’architecture contemporaine nationale et internationale Palais de Chaillot
place du Trocadéro (à gauche)
PARIS 75016
01 58 51 52 00
Bibliothèque municipales générale http://www.paris.fr/bibliotheques
Bibliothèque du conservatoire national des arts et métiers arts et métiers 292 rue Saint Martin 75003 PARIS Tél. : 01 40 27 27 03

CNAM

 Centre de ressource à la Gaîté lyrique Art numérique 3bis rue Papin, 75003 Paris
01 53 01 51 51
www.gaite-lyrique.net

IRCAM

 

Musique 1, place Igor-Stravinsky
75004 PARIS 01 44 78 12 40http://www.ircam.fr/
Cité de la musique Musique 211 avenue Jean Jaurés 75019 PARIS
01 44 84 44 84
Centre de ressource Ensci, Design industriel et général 48 rue Saint Sabin,
75011 PARIS
01 49 23 12 40 www.ensci.com
MatériO Paris ressources en matériaux innovants 74, rue du faubourg Saint-Antoine, 75012 Paris www.materio.com/
Innovathèque ressources en matériaux innovants 10, avenue St Mandé, 75012 http://www.innovathequectba.com

 

Bibliothèque online

Nom Domaines, spécialités Adresse
Remarque
Gallica Général http://gallica.bnf.fr/
BN-OPALE PLUS – BnF Général 13mio

documents

http://catalogue.bnf.fr/
HAL d’articles scientifiques de niveau recherche http://hal.archives-ouvertes.fr/
Yasmin moderated list for art-science-technology interactions around the Mediterranean sea http://www2.media.uoa.gr/yasmin/
ACM digtal library d’articles scientifiques de niveau recherche http://dl.acm.org/
Leonardo Leonardo/The International Society for the Arts, Sciences and Technology a global network on interdisciplinary work, creative output and innovation http://www.leonardo.info/
Olats http://www.olats.org/

 

Lieu d’échange et d’écoute

Nom Domaines, spécialités Adresse Remarque
*di*/zaïn Soirées de présentation de projets Souvent au divan du monde, 75018 www.designersinteractifs.org/dizain Participer pour s’entraîner à se présenter
La cantine Un lieu collaboratif pour les acteurs numériques ; produire, tester et diffuser des projets innovants. Camps, Mobile Monday… 39 rue du Caire, 75002 Paris

http://lacantine.org/

Coworking space
Artlab du digitalArti, Prototypage d’installation 13 rue des écluses Saint Martin, 75010 Paris
09 82 36 89 30 www.digitalarti.com/fr/blog/artlab
Aide pour l’éléctronique, Arduino, Rasperry…

 

MAILING LISTES, FORUMS

Nom Domaines, spécialités Adresse
Remarque
RECHERCHE-DESIGN liste de diffusion de la recherche francophone en design listes.univ-paris1.fr/wws/info/recherche-design
Creative applications Projet design interaction http://www.creativeapplications.net
art-sensitif Questions techniques : capteurs & co artsens.org/cgi-bin/mailman/listinfo/sensitif
Processing Code & co forum.processing.org/two/

 

Magazine, Revue spécialisée

Nom Domaines, spécialités Adresse
Remarque
MCD version papier, mensuel sur les arts et cultures numériques, Culture digitale sur internet:

www.digitalmcd.com/

Make DIY http://makezine.com/

 

Introduction à Processing [2.03]

Nota Bene : Cette article est un mise à jour de l’article du 10 Mars 2013 sur l’introduction à Processing. Il reprend les grandes lignes de l’article d’origine mais adapté aux modifications apportées dans Processing 2.0

Processing est un framework de developpement et un langage simplifié de JAVA créé par Benjamin Fry et Casey Reas au MIT Media Lab en 2001. Leur projet était de créer un langage simple, destiné aux graphistes et artistes, afin de répondre à certains de leur besoins tel que la visualisation de données ou la production de visuels génératifs. Processing est le prolongement « multimédia » de Design by numbers, l’environnement de programmation graphique développé par John Maeda au MIT Media Lab.

Processing a bien évolué depuis sa création en 2001 et est devenu un véritable outils de production pour les designer interactif et artistes.

Actuellement à sa version 2.03, processing est devenu une véritable communauté dont chaque membre apporte sa pierre à l’édifice que ce soit dans le framework directement, à travers des librairies ou par des extensions tel que processing.js ou ruby-processing.

Si nous devions faire un rapide état des lieux des principaux langages et/ou frameworks utilisés dans le design génératif ou l’art numérique nous aurions :

  • C/C++ avec des librairies tel que OpenFramework, Cinder…
  • Java en natif ou simplifié avec Processing ou Eclipse
  • Javascript en natif ou via des frameworks tel que Angular.js, Tree.js, Backbone.js…
  • Les langages de type nodal avec VVVV, Max MSP/Jitter ou Pure Data

Il existe évidement de nombreux autres langages utilisés dans l’expérimentation interactive et visuel tel que Flash (même si son utilisation se fait rare de nos jours) ou encore Touch Designer.

Les cours d’option « Digital Lab » à e-art sup Paris porteront sur les expérimentations visuelles et interactives, qu’elles soient sur écran ou hors écran et ce à travers l’utilisation de Processing. Cette option à pour but d’expérimenter de nouvelles techniques de design apporter par le design par le code et de sortir des techniques habituelles de conception. Il sera une initiation à l’expérimentation interactive et aux cours de développement interactif apportés par la filière Design Interactif d’e-art sup.

Cette année (2013-2014) l’enseignement portera essentiellement sur Processing 2.03, la dernière version stable de processing.

Processing et son environnement

En tant que Graphiste, l’interface de processing peut paraitre un peu « aride » lors de son premier lancement. En effet nous sommes tous des habitués de photoshop et ce qui se rapproche le plus du code pour nous reste flash avec son interface proche du tableau de bord d’une formule 1. Et comme pour photoshop ou la formule 1 il va nous falloir comprendre l’interface avant de se lancer dans la production.

L’interface de processing peut se découper en trois zones :
1_environnement

La première, que nous appellerons « header » contient les principaux éléments d’interface à savoir les boutons permettant :

  • 1 : l’execution du code
  • 2 : Stopper l’execution du code
  • 3 : Créer un nouveau sketch
  • 4 : Ouvrir un sketch
  • 5 : Sauvegarder un sketch
  • 6 : Exporter un Sketch (en applet web ou en stand alone)
  • 7 : Sélection du mode (Java, Android, javascript… de nombreux modes sont developpé par la communauté)
  • 8 : Créer une nouvelle tab, ou nouvelle feuille de code pour ce même sketch.

La seconde zone est notre zone de code. C’est ici que nous écrivons notre sketch.

Enfin la troisième zone est notre console de debug. C’est dans cette zone que s’affichera toute les données que nous demanderons d’afficher afin de debuguer notre sketch. Cette zone permet d’obtenir des informations textuelles sur un sketch sans que celles-ci soient affichées sur notre création.

Lorsque nous faisons « Executer le Code » ou ctrl+R (cmd+R pour OSX) une nouvelle fenêtre s’affiche. C’est notre fenêtre d’exécution. C’est ici que notre code s’exécute et affiche sa traduction graphique.

Maintenant que nous avons vu l’interface nous reste à faire un tour dans la façon dont processing fonctionne. Nous n’avons fasse à nous qu’une simple zone d’écriture, or nous voulons dessiner.

Il faut donc avant tout comprendre comment processing fonctionne afin de pouvoir traduire nos visuels en design par le code.

Si nous devions faire un rapprochement avec un des logiciels que nous connaissons en graphisme alors ce serait Illustrator. En effet, tout comme illustrator, il faut savoir que processing est un environnement de dessin vectoriel.

Et tout comme illustrator (et les autres logiciels de la suite adobe) Processing possède un système de cordonnées cartésien qui nous permet de situer un point dans l’espace (x, y, z).

Enfin, tout comme les logiciels que nous connaissons en design, l’origine de ce repère cartésien se trouvera en haut à gauche de notre fenêtre d’exécution.

2_environnement

Processing : les bases

Dans son écriture, un sketch processing s’articule principalement entre deux fonctions.

La première : setup(), est la fonction constructeur. Cette fonction, appelée une seule fois lors du lancement du sketch permettra de définir la taille de notre skecth, ses propriétés de rendu, de frame rate, de position….

La seconde : draw() est la boucle, c’est dans cette fonction, qui se rappelle à chaque fois que la précédente est terminée, que nous dessinerons. Par défaut processing s’exécute avec un frame rate de 6O, c’est à dire que en 1 seconde la boucle draw() se sera effectuée 60 fois.

Tout langage de programmation repose sur des notions communes à tous, tous se rapportant aux mathématiques. Il est important de maitriser ces notions de bases afin de pouvoir faire ce que l’on veut de processing et non l’inverse.

La première notion de base et la notion de variables commune à tout les langages. Une variable est un « mot clé » associé à une valeur. Cela permet de stocker en mémoire des valeurs sous un nom.

Par exemple si je place deux points de cordonnées 10, 10 et 20, 20 il me sera difficile en code de changer les coordonnées de mon deuxième point car je ne sais pas comment les appeler. Or, si je dis que mon premier point est placé en coordonnée x1, y2 et le seconde x2, y2 et que je défini par la suite x1 = 10 , y1= 10 et x2 = 20, y2 = 20 il m’est alors facile de changer la valeur de x1 en 30.

Il existe plusieurs types de variables dans processing mais nous ne commencerons ici que par les principales. Nous avons donc :

  • boolean etat = true; Variable de type boolean ou binaire, il s’agit d’une variable ne pouvant renvoyer que deux valeurs : Vrai/Faux. Elle permet de faire des comparaisons par exemple « Cette robe est-elle rouge? » est un question demandant un réponse booleenne. « Oui elle est rouge » « non elle ne l’est pas »
  • int x = 10; est une variable renvoyant un nombre entier
  • float x = 3.141592; est une variable renvoyant un nombre décimal
  • String nom = « moi »; est une variable renvoyant une chaine de caractères, elle permet de stocker des mots.
  • char nom = « a »; est une variable ne pouvant stocker que un caractère.

La seconde notion commune est la notion d’opérateurs. Ils permettent d’effectuer divers calculs ou comparaisons. Nous sommes ici dans des notions de bases des mathématiques.

Nous avons différents types d’opérateurs.

  • +, , *, / qui permettent d’addition, soustraire, multiplier ou diviser des valeurs.
  • % le modulo qui permet de connaitre la valeur d’un reste d’une division (valeur résiduelle) par exemple 17%3 = 2 car 2 est le reste de la division de 17/3

Les opérateurs d’assignation :

  • = permet d’attribuer une valeur à un variable
  • +=, -=, *=, /= permettent d’incrémenter, soustraire, multiplier ou diviser des valeurs par exemple x = x+1 est la même chose que x +=1;

Les opérateurs relationnels

  • >, <, <=, =>, == , != permettent de comparer deux valeurs afin de savoir si celles-ci sont supérieur, inférieur, supérieur ou égale, inférieur ou égale, égale ou différentes. Ces opérateur ne renvoi que des valeurs booléennes, c’est à dire vrai ou faux. Par exemple 1 == 2 renverra la valeur « false » car 1 n’est pas égale à 2;

Les opérateurs logiques

  • &&, || permettent d’effectuer des opérations booléenne de type ET et OU.

Enfin les dernières notions communes aux trois quart des langages et indispensable sont les structures conditionnelles et itératives.

Les structures conditionnelles permettent de comparer des valeur de d’attribuer des conditions.

Par exemple j’aimerai pouvoir marcher que si mes lacets sont fait sinon je tombe pourrai s’écrire :

boolean lacetsFait = true; //ici mes lacets sont fait, si lacetFait = false alors il ne sont pas fait

boolean tombe; //ici la variable booléenne n'est pas encore définie car je ne sais pas encore si je vais tomber ou pas

if(lacetFait == true)
{
tombe = false;
}
else{
tombe = true;
}

En mathématique, un itération est l’action de répéter un processus. La structure itérative permet d’effectuer une suite d’opération. Très utiliser pour effectuer plusieurs actions simultanées.

Par exemple, si je désire dessiner des lignes verticales de 100 pixels de haut et ce tout les 10 pixels de large je pourrais très bien écrire :

line(0, 0, 0, 100);
line(10, 0, 10, 100);
line(20, 0, 20, 100);
line(30, 0, 30, 100);
line(40, 0, 40, 100);
…

Et ce pour autant de largeur que je veux pour mon dessins. C’est assez simple pour un dessin de 100 pixel de large mais que ce passe-t-il pour 1920 pixels de large.

Je ne vais pas écrire 1920/10, soit 192 fois la même ligne? C’est là où la structure itérative entre en action.

Elle va me permette de dire que pour une variable i égale 0 (mon départ); i toujours inférieur à 1920 (la limite de mon dessin) et i s’incrémentant de 10 pixels (cad pour i=0 puis 10, puis 20…)

alors je dessine mes lignes. Ce qui donne

for(int i = 0; i<1920; i+=10)
{
line(i, 0, i, 100);
}

Et me voila avec un dessin de 192 lignes de 100 pixels de haut espacées de 10 pixels entre elles. Bref l’itération est notre amie.

Nous venons donc de voir les bases communes aux trois quart des langages de programmation, il est grand temps d’entrer dans le vif du sujet avec les bases de processing.

Comme nous avons vu au début, processing est un dérivé du JAVA, il s’agit d’une version simplifiée de ce langage. Il nous offre donc plusieurs fonctions permettant de dessiner en quelques secondes.

Les deux premières fonctions que nous allons voir permettent de définir la taille de notre sketch, son moteur de rendu et son antialiasing.

Ces deux fonctions seront appelées dans le setup() de notre sketch.

Nous savons que processing est un environnement vectoriel mais de base il va tenter d’économiser un maximum de mémoire. C’est la raison pour laquelle il possède différents moteurs de rendu permettant de dessiner avec plus ou moins de lissage ou seulement en 2D ou avec de la 3D. Processing 2 possède 2 moteurs de rendus (basés sur OpenGL) :

P2D – moteur de rendu de processing pour des dessins 2D.

P3D – moteur de rendu 3D de processing.

Même si nous avons appelé un moteur de rendu performant (2D ou 3D) nous nous rendrons compte que notre ligne reste pixelisée. Cela parce que nous devrons appeler l’antialiasing afin de lisser notre dessin.

Voici donc comment ces fonctions vont s’écrire :

size(largeur, hauteur, moteur de rendu); //permet de définir la taille du sketch et son moteur de rendu, par exemple size(100, 200, JAVA2D);

smooth(); //permet d'activer l'antialiasing

Les prochaines fonctions que nous verrons sont des fonctions de dessins.

background(valeur, valeur, valeur) permet de définir la couleur de notre fond.

Attardons-nous un peu sur cette fonction.Celle-ci est assez particulière car il s’agit d’une fonction permettant de libérer de la mémoire. Nous le savons, notre code se lit successivement en boucle. Cela veut dire que processing va dessiner 25 fois par seconde. Imaginons un sketch de 710*200 où nous dessinerons un cercle de taille 20 à la position de la souris et ce sans background :

void setup()
{
size(710, 200, JAVA2D);
smooth();
}

void draw()
{
ellipse(mouseX, mouseY, 20, 20);
}

ixd_processingIntro_00

lors de l’exécution nous remarquons qu’au fur est à mesure les cercles que nous dessinons restent sur scène et forment une trainée. Nous savons que processing exécute la boucle draw 25 par seconde, or cette boucle dit de dessiner un cercle à la position de la souris. Il est donc logique que en 1 seconde j’ai 25 cercles, en 2seconde 50 et ainsi de suite…. Au bout d’un moment notre ordinateur va se mettre à chauffer et c’est normal car nous demandons pas mal de calculs.

C’est là que la fonction background entre en fonction. En insérant un background (noir par exemple) dans mon draw, je demande a processing d’effacer tout les cercle précédents sur scène pour n’afficher que celui de la boucle actuelle. Cela aura pour effet de nettoyer la mémoire de processing.
Cela nous donnera :

ixd_processingIntro_01

Pour écrire un background dans ma boucle draw j’utiliserai la syntaxe suivante :

background(valeur, valeur, valeur);

Attardons nous un peu sur ces « valeurs » entre parenthèse. Processing me demande 3 valeurs pour définir la couleur de mon fond. Par défaut processing fonctionne en RVB, c’est à dire qu’il chaque couleurs est définie par ces valeurs rouge, verte et bleu, chacune d’entre-elles étant définie entre 0 et 255.

Ainsi background(0, 0, 0) sera noir alors que background(255, 0, 0) sera rouge.

Il faut aussi savoir que processing permet de simplifier son code. Ainsi si nous mettons background(0) processing comprendra que nos 3 valeurs seront égales à 0

Entrons maintenant plus en détail dans les outils de dessin. Processing va nous permettre de dessiner ce que l’on veut mais il nous offre aussi des fonctions pour dessiner des formes géométriques simples. Ainsi nous avons :

point(x, y); //qui permet de dessiner un point dans un espace 2D

point(x, y, z); //qui permet de dessiner un point dans un espace 3D

rect(x, y, largeur, hauteur); //permettant de dessiner un rectangle

ellipse(x, y, largeur, hauteur); //permettant de dessiner une ellipse	

quad(x1, y1, x2, y2, x3, y3, x4, y4); // permettant de dessiner un quadrilatère simple

triangle(x1, y1, x2, y2, x3, y3); //permettant de dessiner un triangle.

line(x1, y1, x2, y2); //permettant de dessiner une ligne dans un espace 2D

line(x1, y1, z1, x2, y2, z2);//permettant de dessiner une ligne dans un espace 3D

//Il existe d'autre formes simple tels que les arcs ou les courbes de bézier

Enfin, pour terminer ce chapitre d’initiation sur processing regardons un peu les deux éléments de base qu’il nous manque pour faire notre premier dessin.

Nous avons vu les notions de base communes aux trois quarts des langages, nous avons vu les deux fonctions de base de processing setup() et draw() permettant de définir notre skecth et de dessiner à l’intérieur. Nous avons aussi vu comment définir la taille de notre sketch, son moteur de rendu, son antiliasing et comment dessiner un fond et des formes simples. Autrement dis il ne nous manque que de la mise en forme de ces dessins, à savoir comment définir leurs couleurs de fond et/ou de contour.

Pour cela processing nous met à disposition deux fonctions :

fill(valeur, valeur, valeur); //permet de définir la couleur de fond d'un objet en RVB, cette fonction doit être écrite avant la forme.

stroke(valeur, valeur, valeur); permet de définir la couleur de contour d'un objet en RVB, cette fonction doit être écrite avant la forme.

Mais que ce passe-t-il dans le cas où je veuille avoir un fond ou un contour avec de l’alpha? Qu’à cela ne tienne, processing à tous prévu. Il nous suffira d’ajouter une quatrième valeur à notre fill() ou notre stroke. Nous aurons ainsi défini des valeurs en RVB+Alpha. Mais n’oublions pas une chose, processing fonctionne sur des valeurs RGB de 0 à 255, il en va de même pour l’alpha. Ainsi un alpha de 50% correspondra à la valeur 255/2 soit 127.

enfin dans le cas où je ne veuille pas mettre de fond ou de contour à mon dessin il nous suffira d’écrire l’une des fonctions suivante : noFill() ou noStroke();

Nous avons donc vu toute les bases de processing pour effectuer notre premier skecth. Profitons en alors pour faire notre premier dessin en utilisant ce que l’on connait.

Nous allons dessiner une sketch de 710*500, contenant un grille de point espacer de 10 pixels chacun et avec un cercle de 100 pixel de diametre sans contour et de remplissage blanc à 50% d’alpha.

Cela nous donne donc

int space = 10;

void setup()
{
size(710, 500, P2D);
smooth();
}

void draw()
{
background(0);
for(int i = 0; i<width; i+=space)
{
for(int j = 0; j<height; j+=space)
{
stroke(255);
point(i, j);
}
}

noStroke();
fill(255, 127);
ellipse(width/2, height/2, 100, 100);
}

ce qui nous donnera :

ixd_processingIntro_02

4DI – sujet #1 Muséographie digitale et interactive – Références et conférences

Pour accompagner vos réflexions sur le sujet voici quelques références et conférences.

Référence :

Points [par Breakfast NY] : Signalétique digitale reprenant les codes de la signalétique urbaine. Points est un panneau directionnel connecté permettant de diffuser différente informations de direction en fonction de ce que cherche l’utilisateur (Villes, Conférences, transport, événements…)

Level Green [Art+Com]
http://www.artcom.de/en/projects/project/detail/level-green/

Experience Abstract Information [Stefan Kuzaj + Jocehn Winker] où comment rendre des flux d’informations immersifs
https://vimeo.com/9468855

Conférences

Julien Dorra, « muséomix, reinventons le musée », TEDx Paris

Younes Duret, « Le nouveau design émanant des pays arabes », TEDx Rabat

Joachim Sauter, Art+Com, Resonate 2013

Joachim Sauter at Resonate 2013 from Resonate Festival on Vimeo.

Design Génératif et Expériences Interactives [Introduction et support]

Lors du premier cours de l’année en option digital lab, les étudiants ont découvert ce qu’était le design génératif et l’expérimentation interactive au travers divers rappels historique.

Ce rappel historique porte à la fois sur les grandes évolutions technologiques, théoriques et de langages de programmation que sur de grandes pièces de l’art numérique ou de son application au travers de la communication visuelle.

Le document ci-joint, support de cours, présente différentes frises chronologiques non-exhaustif, ainsi qu’une présentation du processus créatif génératif. La fin de ce document présente aussi de nombreux liens vers des conférences, interviews, films..

DigitalLab_DesignGeneratifEtExperimentationsInteractives

Programmation orientée objet & Générateur de Particules

La programmation orientée objet est l’une des bases de programmation moderne et se retrouve dans un grand nombre de langages de programmation. Elle permet de créer des « objets » mais aussi de nous simplifier grandement la vie.

Prenons un sketch simple de 200*200 dans lequel nous souhaitons dessiner une balle se déplaçant à une vitesse aléatoire et rebondissant sur les bords de notre scène. Nous écrirons :

//Variables de notre balles
float x; //position x de la balle
float y; //position y de la balle
float vx; //vitesse x de la balle
float vy; //vitesse y de la balle
float t; // taille de la balle

void setup()
{
  size(200, 200, JAVA2D);
  smooth();
  
  //initialisation des variables.
  t = random(5, 10);
  x = random(t, width-t);
  y = random(t, height-t);
  vx = random(-3, 3);
  vy = random(-3, 3);
}

void draw()
{
  background(255);
  
  //mise à jour des variables
  x += vx;
  y += vy;
  
  //detection des murs
  if(x <= t || x >= width-t)
  {
    vx = -vx;
  }
  if(y <= t || y >= height-t)
  {
    vy = -vy;
  }
  
  ellipse(x, y, t*2, t*2);
}

ixd_classe_00-01

Mais que ce passe-t-il si nous voulons non plus une balle mais 10, 40 ou 100?
La première solution consisterait à créer autant de variables que nous avons de balles mais cela n’est pas envisageable. Notre code dépassera de loin le nombre de page de la constitution américaine et nous serons vite perdu.
La seconde solution serait d’utiliser des tableaux pour stocker chacune de nos variables mais là aussi nous allons avoir de nombreux tableaux et cela n’est toujours pas envisageable.
C’est là que la programmation orientée objet entre en jeux. Elle va nous permettre de créer aisément nos 10, 40 ou 100 balles en un minimum de temps.

Qu’est ce qu’un objet?

Un objet est directement inspiré du monde réel. En effet nous sommes entouré d’objets et chacun de ces objets à des propriétés propres. Par exemple nous avons les objets « téléphones ». Chacun de ses objets « téléphones » sont définis par le fait qu’ils ont tous des variables communes, à savoir le fait de pouvoir passer des communications vocales par le biais d’un réseau téléphonique. Mais ils possèdent aussi des variables qui leur sont propre comme le fait de pouvoir envoyer des sms ou non, d’être tactile ou pas, d’avoir des poids et des tailles qui diffèrent… Bref nos téléphones sont des objets appartenant à une même classe, la classe « Téléphone ».

Revenons maintenant à notre skecth, nous avons imaginé avoir un espace de 200*200 dans lequel des balles de tailles différentes se déplacent à des vitesses différentes. Nous avons donc des « objets » qui ont un comportement commun, à savoir le fait d’être des balles et de se déplacer, mais aussi différents puisque leur vitesses diffèrent. Nous avons l’exemple parfait pour créer une « classe » d’objets.

Notre première classe.

Précédemment nous avons parlé de « classe » sans même l’expliquer. Une classe est une matrice, un patron, un plan de construction… d’un objet. La classe d’un objet permet de créer et d’instancier les variables communes à tous nos objet, de développer leur comportements et de créer la méthode de construction de notre objet. D’un point de vu développement, elle est assez proche de la façon dont nous construisons un sketch.

Lorsque nous créons nos sketchs nous avons l’habitude de créer des variables, puis une méthode setup() qui nous permettra de créer notre sketch (taille, moteur de rendu…) et enfin une méthode draw() (notre boucle). Lorsque nous créons une classe nous obtenons à peu de choses près la même syntaxe :

class maClasse
{
	//Ici mes variables

	//Mon constructeur, l'équivalent de mon setup() pour mon sketch
	Balle()
	{
	}

	//Mes méthode
	void display()
	{
	}
}

Entrons maintenant dans le vif du sujet en créant notre classe « Balle ». Pour cela nous allons reprendre l’ensemble de nos variables, nous les initialiserons dans notre constructeur et enfin nous dessinerons notre balle dans une méthode display(). Cela nous donne donc :

class Balle
{
  //variables globales
  float x; //position x de la balle
  float y; //position y de la balle
  float vx; //vitesse x de la balle
  float vy; //vitesse y de la balle
  float t; // taille de la balle

  //Mon constructeur, celui doit toujours avoir le même nom que la class afin d'être reconnu comme un constructeur.
  Balle()
  {
    //initialisation des variables.
    t = random(5, 10);
    x = random(t, width-t);
    y = random(t, height-t);
    vx = random(-3, 3);
    vy = random(-3, 3);
  }

  void display()
  {
    //mise à jour des variables
    x += vx;
    y += vy;

    //detection des murs
    if (x <= t || x >= width-t)
    {
      vx = -vx;
    }
    if (y <= t || y >= height-t)
    {
      vy = -vy;
    }
    fill(255);
    stroke(0);
    ellipse(x, y, t*2, t*2);
  }
}

Nous venons d’écrire notre classe mais comme nous avons vu, il s’agit ici de la matrice qui nous permettra de créer nos objets.
Il nous faut maintenant créer nos objets et les instancier pour ensuite les afficher sur la scène. Dans notre sketch, nous allons créer un nouvel objet « maBalle » faisant partie de la classe « Balle ».

Balle maBalle;

Dans notre setup() nous instancierons notre objet.

maBalle = new Balle();

Enfin dans notre draw, nous appellerons la méthode display() de notre balle

maBalle.display();

Cela nous donne donc

Balle maBalle;

void setup()
{
  size(400, 200, JAVA2D);
  smooth();
  maBalle = new Balle();
}

void draw()
{
  background(255);
  maBalle.display();
}

class Balle
{
  //variables globales
  float x; //position x de la balle
  float y; //position y de la balle
  float vx; //vitesse x de la balle
  float vy; //vitesse y de la balle
  float t; // taille de la balle

  //Mon constructeur, celui doit toujours avoir le même nom que la class afin d'être reconnu comme un constructeur.
  Balle()
  {
    //initialisation des variables.
    t = random(5, 10);
    x = random(t, width-t);
    y = random(t, height-t);
    vx = random(-3, 3);
    vy = random(-3, 3);
  }

  void display()
  {
    //mise à jour des variables
    x += vx;
    y += vy;

    //detection des murs
    if (x <= t || x >= width-t)
    {
      vx = -vx;
    }
    if (y <= t || y >= height-t)
    {
      vy = -vy;
    }
    fill(255);
    stroke(0);
    ellipse(x, y, t*2, t*2);
  }
}

ixd_classe_00-01

On remarque que pour appeler une méthode de notre classe nous procédons de la manière suivante objet.methode(). Il en va de même pour accéder aux variables. Ainsi println(maBalle.x) me renverra la valeur x de ma balle. On peut par la suite aisément modifier ces valeurs.

On avait pas parlé de plusieurs balles?

Nous venons de créer notre première classe mais nous obtenons 1 balle et non 100 comme promis précédemment. Pour cela rien de plus simple, et c’est là le grand pouvoir des classes, il nous suffit de créer un tableau d’objets et non plus un seul objet.

Balle[] maBalle;
int nbBalle;

void setup()
{
  size(200, 200, JAVA2D);
  smooth();
  nbBalle = 100;
  maBalle = new Balle[nbBalle];
  for(int i = 0; i < maBalle.length; i++)
  {
    maBalle[i] = new Balle();
  }
}

void draw()
{
  background(255);
  for(int i = 0; i < maBalle.length; i++)
  {
    maBalle[i].display();
  }
}

ixd_classe_02

Allons plus loin dans la possibilité qu’offrent les classes.

Nous savons maintenant comment créer une classe et instancier des objets mais allons plus loin dans les possibilités que nous offre la programmation orientée objet. Nous venons de créer 100 objets mais que ce passe-t-il dans le cas où nous voulions en créer à chaque clic?
Il faudrait que nous rajoutions des éléments à notre tableau.

Imaginons un sketch simple, de 400*200 dans lequel nous pourrions rajouter une balle à chaque clic dont la position de départ sera définie par la position de la souris.

Pour cela nous allons toujours utiliser un tableau mais d’un autre type puisque nous utiliserons un ArrayList(). À l’inverse d’un tableau classique maBalle[] qui possède une taille fixe, les ArrayList sont des tableaux dont la taille n’est pas définie et peut être en constante expansion. Cependant sa déclaration diffère d’un tableau classique. Ainsi pour passer notre classe balle en ArrayList pour devrons changer notre code comme il suit :

ArrayList<Balle> maBalle;
int nbBalle;

void setup()
{
  size(200, 200, JAVA2D);
  smooth();
  nbBalle = 100;
  maBalle = new ArrayList<Balle>();
}

De même la methode nous permettant d’ajouter des balles sur notre scène diffère d’un tableau classique

for (int i=0; i<nbBalle; i++)
  {
    maBalle.add(new Balle());
  }

Afin la dernière différence avec le tableau classique est la façon nous nous parcourons notre ArrayList afin d’appeler la méthode display() de notre classe.

for(int i = 0; i < maBalle.size(); i++)
  {
    Balle b = maBalle.get(i);
    b.display();
  }

Maintenant que nous avons vu comment créer et parcourir un ArrayList, il ne nous reste plus qu’à modifier notre code précédent afin de créer nos balles à chaque clic de la souris

ArrayList<Balle> maBalle;
int nbBalle;

void setup()
{
  size(400, 200, JAVA2D);
  smooth();
  nbBalle = 10;
  maBalle = new ArrayList<Balle>();
  for (int i=0; i<nbBalle; i++)
  {
    maBalle.add(new Balle()));
  }
}

void draw()
{
  background(255);
  
  if(mousePressed)
  {
     maBalle.add(new Balle());
  }
 
  for(int i = 0; i < maBalle.size(); i++)
  {
    Balle b = maBalle.get(i);
    b.display();
  }
}

Le résultat est là mais cependant il nous manque une règle à respecter. Nous voulions que nos balles apparaissent à la position de la souris, hors pour le moment elle apparaissent à des positions aléatoire. En effet lorsque l’on retourne dans le constructeur de la classe nous remarquons que notre x et y sont définies de manière aléatoire.

Balle()
  {
    //initialisation des variables.
    t = random(5, 10);
    x = random(t, width-t);
    y = random(t, height-t);
    vx = random(-3, 3);
    vy = random(-3, 3);
  }

Nous pourrions changer cela par un mouseX mouseY mais en faisant cela nos 10 première balles présentes sur scène auront le même point d’origine. Nous allons donc légèrement changer notre constructeur en lui indiquant qu’à son appel il devra recevoir des variables. Nous pourrons alors par la suite définir aisément la position de chacune de nos balles à la création.

Balle(float x_, float y_)
  {
    //initialisation des variables.
    t = random(5, 10);
    x = x_;
    y = y_;
    vx = random(-3, 3);
    vy = random(-3, 3);
  }

Il nous faudra alors changer la façon dont nous instancions nos balles comme il suit

maBalle.add(new Balle(position x, position y));

Nous avons maintenant un tableau dynamique (ArrayList) et nous créons autant de balles que nous voulons.
Ajoutons maintenant une dernière possibilité, à savoir fixer une limite à notre tableau dynamique. En effet cela nous permettra d’économiser notre ordinateur qui risquerait, au bout d’un millions de particules, de ralentir.

Pour cela nous rajoutons une condition permettant de supprimer la première balle (la plus vieille) créée lorsque nous atteignons un maximum de 30 balles

if(maBalle.size() >= 30)
    {
      maBalle.remove(0);
    }

Nous voila donc avec notre première classe de particules.

ArrayList<Balle> maBalle;
int nbBalle;

void setup()
{
  size(400, 200, JAVA2D);
  smooth();
  nbBalle = 10;
  maBalle = new ArrayList<Balle>();
  for (int i=0; i<nbBalle; i++)
  {
    maBalle.add(new Balle(random(width), random(height)));
  }
}

void draw()
{
  background(255);
  
  if(mousePressed)
  {
     maBalle.add(new Balle(mouseX, mouseY));
  }
  
  if(maBalle.size() >= 30)
    {
      maBalle.remove(0);
    }
  
  for(int i = 0; i < maBalle.size(); i++)
  {
    Balle b = maBalle.get(i);
    b.display();
  }
}

class Balle
{
  //variables globales
  float x; //position x de la balle
  float y; //position y de la balle
  float vx; //vitesse x de la balle
  float vy; //vitesse y de la balle
  float t; // taille de la balle

  //Mon constructeur, celui doit toujours avoir le même nom que la class afin d'être reconnu comme un constructeur.
  Balle(float x_, float y_)
  {
    //initialisation des variables.
    t = random(5, 10);
    x = x_;
    y = y_;
    vx = random(-3, 3);
    vy = random(-3, 3);
  }

  void display()
  {
    //mise à jour des variables
    x += vx;
    y += vy;

    //detection des murs
    if (x <= 0 || x >= width)
    {
      vx = -vx;
    }
    if (y <= 0 || y >= height)
    {
      vy = -vy;
    }
    fill(255);
    stroke(0);
    ellipse(x, y, t*2, t*2);
  }
}

ixd_classe_03

Nous venons de voir comment créer et utiliser des objets pour créer un premier générateur de particules. Cependant nous pouvons aller beaucoup plus loin avec les classes en créant des objets réagissant entre eux et ayant leur propres comportement.

Imaginons un sketch dans lequel se déplaceraient des atomes. Chaque atome est défini par une masse aléatoire qui influencera sa vitesse, plus sa masse sera grande plus il sera lent. Chacun d’entre eux possédera un nombre aléatoire d’électrons gravitant autour d’eux. Ces atomes se déplaceront sur la scène et rebondiront sur les « murs » de celle-ci. Leurs électrons tourneront autour en se rapprochant de l’atome puis en s’en éloignant jusqu’à une certaines limite définie par la masse de l’atome.

Lorsque deux atomes entrent en collision ceux-ci créaient alors un nouvel atome au design différent dont la masse sera l’addition de la masse des deux atomes d’origine. De même le nombre d’électrons gravitant autour de ce nouvel atome sera défini par l’addition du nombre d’électrons gravitant autour des deux atomes d’origine.

Enfin lorsque deux nouveaux atomes entrent en collision, ils se divisent pour recréer les deux atomes d’origines qui les composaient.

atoms

Un skecth comme celui ci est l’exemple parfait pour réaliser des classes. La première sera notre classe Atoms qui aura ses caractéristique propre :

• Position
• Masse
• Vitesse
• Nombre d’électrons

La seconde sera notre classe NewAtoms qui aura les caractéristiques suivante :

• Position (Position de la collision des deux atomes d’origine)
• Masse (Addition des deux masses)
• Vitesse
• Nombre d’électrons (Addition des électrons)

Nous obtenons donc les classe suivantes :

class Atom
{
  //variables de l'atome
  float x, y; //position de l'atome
  float masse; //masse de l'atome, ici son diamètre
  float velocite, vx, vy; //velocité des atome, celles-ci seront dépandantent de la masse des atomes
  color cF, cS; //couleur de l'atome

  //variable des electrons
  int nbElectrons;
  float[] posX; //position des electrons
  float[] posY; //position des electrons
  float[] r; //rayon des electrons sur le cercle trigonométrique
  float[] angle; //angle des electrons sur le cercle trigonométrique
  float[] vr; //vitesse des electrons
  float[] vAngle; //vitesse de l'angle des electrons
  float[] origin; //rayon d'origine qui établir notre limite haute de mouvement

  //constructeur
  Atom(float _x, float _y, float _masse, int _nbElec)
  {
    this.x = _x;
    this.y = _y;
    this.masse = _masse;
    this.cF = color(random(160, 180), random(70, 100), random(2, 10), random(70, 100));
    this.cS = color(random(160, 180), random(70, 100), random(10, 20), random(70, 100));

    //calcul des velocités
    this.vx = this.x;
    this.vy = this.y;
    this.velocite = map(this.masse, 5, 30, 5, 1);
    this.vx = this.velocite*random(-1, 1);
    this.vy = this.velocite*random(-1, 1);

    //electrons
    createElectron(_nbElec);
  }

  //--------
  //methodes
  //--------

  //constructeur des Electrons
  void createElectron(int _nbElectrons)
  {
    nbElectrons = _nbElectrons;
    posX = new float[nbElectrons];
    posY = new float[nbElectrons];
    r = new float[nbElectrons];
    angle = new float[nbElectrons];
    vr = new float[nbElectrons]; 
    vAngle = new float[nbElectrons];
    origin = new float[nbElectrons]; 
    for (int i=1; i<nbElectrons; i++) { 
      angle[i] = ((2*PI)/nbElectrons)*i; //Ici nous divisons le cercle 2PI par le nombre de particule multiplier par i pour obtenir l'angle de chaque particule et les placer à équidistance les une des autres
      vr[i] = random(-0.5, -0.1);
      vAngle[i] =radians(1);// radians(random(-1, 1));
      r[i] = random(this.masse/2, this.masse);
      origin[i] = r[i]; 
      posX[i] = this.x+cos(angle[i])*r[i]; //position x sur le cercle x = cos(angle)*rayon
      posY[i] = this.y+sin(angle[i])*r[i]; //position y sur le cercle y = cos(angle)*rayon
    }
  }

  //deplacement
  void motion()
  { 
    this.x += this.vx; 
    this.y += this.vy;
  }

  void checkEdge()
  {
     if (this.x<0)
    {
      this.x = 1;
      this.vx *=-1;
    }
    else if(this.x>width)
    {
      this.x = width-1;
      this.vx *=-1;
    }

    if (this.y<0)
    {
      this.y = 1;
      this.vy *=-1;
    }
    else if(this.y>height)
    {
      this.y = height-1;
      this.vy *=-1;
    }
  }

  //dessin et mouvement des electrons
  void electrons()
  {
    for (int i=1; i<nbElectrons; i++) { 
      r[i] += vr[i]; // reduction du rayon
      angle[i] += vAngle[i];
      if (r[i]>origin[i]+1 || r[i]<this.masse/2-1)
      {
        vr[i] *=-1;
      }
      //position des partciules
      posX[i] = this.x+cos(angle[i])*r[i];
      posY[i] = this.y+sin(angle[i])*r[i];
      stroke(0, 0, 100, 30);
      noFill();
      //dessin de la fleur
      ellipse(posX[i], posY[i], 3, 3);
      line(posX[i], posY[i], this.x, this.y);
      if (i != nbElectrons-1)
      {
        line(posX[i], posY[i], posX[i+1], posY[i+1]);
      }
      else
      {
        line(posX[i], posY[i], posX[1], posY[1]);
      }
    }
  }

  //affichages
  void display()
  {

    //dessin l'atome
    //noStroke();
    electrons();
    fill(0, 0, 100, 5);//this.cF);
    stroke(0, 0, 100, 20);//this.cS);
    ellipse(this.x, this.y, this.masse/2, this.masse/2);
    point(this.x, this.y);
  }
}
class newAtom
{
  //variable du nouvel atome
  float x, y; //position du nouvel atome
  float masse; //masse du nouvel atome
  float velocite, vx, vy; //velocité des atome, celles-ci seront dépandantent de la masse des atomes

  //valeur d'origine de l'atome
  float masse1, masse2;
  int nbElec1, nbElec2;


  //variable des electrons
  int nbElectrons;
  float[] posX; //position des electrons
  float[] posY; //position des electrons
  float[] r; //rayon des electrons sur le cercle trigonométrique
  float[] angle; //angle des electrons sur le cercle trigonométrique
  float[] vr; //vitesse des electrons
  float[] vAngle; //vitesse de l'angle des electrons
  float[] origin; //rayon d'origine qui établir notre limite haute de mouvement

  //constructeur
  newAtom(float _x, float _y, float _masse, int _nbElec, float _masse1, float _masse2, int _nbElec1, int _nbElec2)
  {
    this.x = _x;
    this.y = _y;
    this.masse = _masse;

    //origine de l'atome
    this.masse1 = _masse1;
    this.masse2 = _masse2;
    this.nbElec1 = _nbElec1;
    this.nbElec2 = _nbElec2;

    //vitesse
    this.velocite = map(this.masse, 10, 60, 3, 1);
    this.vx = this.velocite*random(-1, 1);
    this.vy = this.velocite*random(-1, 1);

    //electrons
    createElectron(_nbElec);
  }

  //--------
  //methodes
  //--------
  //constructeur des Electrons
  void createElectron(int _nbElectrons)
  {
    nbElectrons = _nbElectrons;
    posX = new float[nbElectrons];
    posY = new float[nbElectrons];
    r = new float[nbElectrons];
    angle = new float[nbElectrons];
    vr = new float[nbElectrons]; 
    vAngle = new float[nbElectrons];
    origin = new float[nbElectrons]; 
    for (int i=1; i<nbElectrons; i++) { 
      angle[i] = ((2*PI)/nbElectrons)*i; //Ici nous divisons le cercle 2PI par le nombre de particule multiplier par i pour obtenir l'angle de chaque particule et les placer à équidistance les une des autres
      vr[i] = random(-0.5, -0.1);
      vAngle[i] =radians(1);// radians(random(-1, 1));
      r[i] = random(this.masse/2, this.masse);
      origin[i] = r[i]; 
      posX[i] = this.x+cos(angle[i])*r[i]; //position x sur le cercle x = cos(angle)*rayon
      posY[i] = this.y+sin(angle[i])*r[i]; //position y sur le cercle y = cos(angle)*rayon
    }
  }

  //deplacement
  void motion()
  { 
    this.x += this.vx; 
    this.y += this.vy;
  }

  void checkEdge()
  {
    if (this.x<0)
    {
      this.x = 1;
      this.vx *=-1;
    }
    else if(this.x>width)
    {
      this.x = width-1;
      this.vx *=-1;
    }

    if (this.y<0)
    {
      this.y = 1;
      this.vy *=-1;
    }
    else if(this.y>height)
    {
      this.y = height-1;
      this.vy *=-1;
    }
  }


  //dessin et mouvement des electrons
  void electrons()
  {
    for (int i=1; i<nbElectrons; i++) { 
      r[i] += vr[i]; // reduction du rayon
      angle[i] += vAngle[i];
      if (r[i]>origin[i]+1 || r[i]<this.masse/2-1)
      {
        vr[i] *=-1;
      }
      stroke(0, 0, 100, 15);
      noFill();
      
      posX[i] = this.x+cos(angle[i])*r[i];
      posY[i] = this.y+sin(angle[i])*r[i];
      ellipse(posX[i], posY[i], 4, 4);
      
      posX[i] = this.x+cos(angle[i])*(r[i]-4);
      posY[i] = this.y+sin(angle[i])*(r[i]-4);
      ellipse(posX[i], posY[i], 2, 2);
      
      posX[i] = this.x+cos(angle[i])*(r[i]+10);
      posY[i] = this.y+sin(angle[i])*(r[i]+10);
      line(posX[i], posY[i], this.x, this.y);
      
    }
  }

  void display()
  {
    electrons();
    fill(0, 0, 100, 10);
  }
}

Il nous faut ensuite créer nos objets, les instancier et calculer leur collisions. Pour ce dernier point nous utiliserons la méthode dist() nous permettant de calculer la distance entre nos objets. Lorsque cette distance sera égale ou inférieure à 0 nous définirons une collision, créerons nos nouveaux atomes et supprimerons les atomes d’origine. Enfin nous calculerons les collisions entre les nouveaux atomes pour faire l’inverse en les supprimant et en recréant nos atomes d’origine

/*"Fusion atome" est un skecth permettant de découvrir la programation orientée objet dans processing
 Dans ce skecth nous verrons comment créer un classe et gérer des comportements
 
 Nous allons créer un système de particule se deplacement à des vitesses et des sens différents et aléatoires.
 Lorsque deux particules se rencontre, une nouvelle se créer. Cette nouvelle particules est le resultat de la fusion des
 deux premières. Lorsque deux particules fusionnées se rencontre elles redeviennent des particules simples.*/
//--------------------------------------------------------------------------------------------------------//


ArrayList<Atom> myAtom; //declaration de mon tableau dynamique, ici nous utilisons un tableau dynamique de sorte à gérer sa taille de manière dynamique.
int nbAtomBegin; //nombre d'atome au départ du sketch

ArrayList<newAtom> myNewAtom; //déclarationd d'un tableau dynamique de nouveau atoms

void setup()
{
  size(displayWidth/2, displayHeight/2, OPENGL);
  smooth(8);
  colorMode(HSB, 360, 100, 100, 100);

  nbAtomBegin = 10; // nombre d'atome au départ
  myAtom = new ArrayList<Atom>(); //création de mon tableau d'atome
  for (int i=0; i<nbAtomBegin; i++)
  {
    myAtom.add(new Atom(random(width), random(height), random(5, 40), int(random(5, 11))));
  }

  myNewAtom = new ArrayList<newAtom>(); //création de mon tableau de nouveaux Atomes
  background(0);

}

void draw()
{
  fill(200, 100, 15, 50);
  noStroke();
  rect(0, 0, width, height);
  
//rotate(radians(50), 1, 0, 0);
  for (int i=0; i<myAtom.size(); i++)
  {
    Atom pi = myAtom.get(i);

    for (int j=i+1; j<myAtom.size(); j++)
    {
      Atom pj = myAtom.get(j);
      float distanceBrute = dist(pi.x, pi.y, pj.x, pj.y);
      float distance = distanceBrute-(pi.masse/2+pj.masse/2);
      //float alphaP = map(d, 0, 50, 100, 0);

      if (distance<=0)
      {
        myNewAtom.add(new newAtom(pi.x, pi.y, pi.masse+pj.masse, pi.nbElectrons+pj.nbElectrons, pi.masse, pj.masse, pi.nbElectrons, pj.nbElectrons));      
        myAtom.remove(pi);
        myAtom.remove(pj);
      }
    }
  }

  for (int i=0; i<myNewAtom.size(); i++)
  {
    newAtom pi = myNewAtom.get(i);

    for (int j=i+1; j<myNewAtom.size(); j++)
    {
      newAtom pj = myNewAtom.get(j);
      float distanceBrute = dist(pi.x, pi.y, pj.x, pj.y);
      float distance = distanceBrute-(pi.masse/2+pj.masse/2);

      if (distance<=0)
      {
        myAtom.add(new Atom(pi.x-10, pi.y-10, pi.masse1, pi.nbElec1)); 
        myAtom.add(new Atom(pi.x+10, pi.y+10, pi.masse2, pi.nbElec2));
        myAtom.add(new Atom(pj.x-10, pj.y-10, pj.masse1, pj.nbElec1)); 
        myAtom.add(new Atom(pj.x+10, pj.y+10, pj.masse2, pj.nbElec2)); 
        myNewAtom.remove(pi);
        myNewAtom.remove(pj);
      }
    }
  }


  //affichage des atomes
  for (Atom a: myAtom)
  {
    a.display();
    a.motion();
    a.checkEdge();
  } 

  for (newAtom na: myNewAtom)
  {
    na.display();
    na.motion();
    na.checkEdge();
  }
}

void mouseReleased()
{

  myAtom.add(new Atom(mouseX, mouseY, random(5, 30), int(random(5, 11))));
}

atoms_billboard

Retour sur les bases de trigonométrie

Il n’est pas rare de voir les étudiants l’air dépités à l’idée de devoir faire de la trigonométrie, et pourtant celle-ci revient très souvent sur le devant de la scène lorsque l’on commence à vouloir faire des attracteurs ou des mouvements circulaires.

La trigonométrie et ses fonctions sont des éléments indispensables quand on commence à vouloir faire des visuels plus évolués. Même si son nom parait horrible et semble nous renvoyer à nos cours de maths de 3ème nous verrons que processing va vite nous la faire re-aimer.

Ici nous nous attarderons sur le cercle trigonométrique qui nous permettra de voir comment on effectue le calcul d’un angle ou même comment positionner un point sur un cercle. En bref nous verrons nos vieux amis des cours de maths que sont sinus, cosinus et tangente.

Nous appliquerons cela à deux skecth. Le premier nous permettra de réaliser un attracteur/repulseur simple. Le seconde, plus appliquer au design génératif nous permettra de découvrir comment réaliser des visuel simple à l’aide de la trigonométrie.

Petit rappel de trigonométrie

La trigonométrie traite des relations entre distances et angles dans les triangles et notamment dans le triangle rectangle. C’est ce dernier qui va nous intéresser plus particulièrement.

Faisons un rapide bon dans le passé concernant ce triangle. Un triangle rectangle se caractérise par un angle droit (90°) et la somme de ses angles est égale à 180°. L’angle droit est donc son angle le plus grand. Enfin le côté opposé à cet angle s’appel l’hypoténuse et se caractérise par le fait qu’il est le côté le plus grand de ce triangle.

Capture d’écran 2013-03-17 à 18.24.47

Si on s’interesse à l’angle BAC et aux fonctions trigonométriques alors nous remarquons que :

  • sin(BAC) = a/c
  • cos(BAC) = b/c
  • tan(BAC) = a/b

Nous avons ici les fonctions auxquelles nous allons prêter attention dans processing.

Allons maintenant plus loin en traçant un cercle dont l’origine sera A et de rayon AB (notre hypothenuse). Partons du principe que notre hypothenuse AB a un valeur de 1. Nous obtenons alors un cercle trigonométrique.

Capture d’écran 2013-03-17 à 18.31.18

Si on observe ce cercle nous remarquons que les cordonnées du point B peuvent être définies de la sorte

  • x = cos(t)
  • y = sin(t)

Or nous savons ici que notre hypothenuse à un valeur de 1. Si nous voulons être correcte dans nos cordonnées, nous obtenons

  • x = cos(t)*1
  • y = sin(t)*1

Nous venons de voir la formule permettant de calculer les coordonnées d’un point sur un cercle. Nous avons ici l’une des formules que nous utiliserons le plus dans processing par la suite.

  • x = cos(angle)*rayon
  • y = sin(angle)*rayon

Nous venons de voir à quoi les fonctions trigonométriques Sinus et Cosinus pouvaient nous être utile, attardons nous maintenant sur la dernière, la Tangente.

La tangente est le rapport du sinus au cosinus, par définition :

  • tan(angle) = sin(angle)/cos(angle)

On appelle tangente de l’angle aigu , le nombre noté tan(angle) défini par BC/AC. Nous verrons par la suite comment nous pouvons nous servir de la tangente pour calculer un angle.

Capture d’écran 2013-03-17 à 18.24.47

Trigonométrie et processing

Nous venons de voir un résumer des règles de trigonométrie dont nous allons nous servir le plus. Retournons maintenant sur processing pour faire un rapide état des lieux des méthodes disponible. Si nous venons de revoir toute ces bases c’est bien parce que nous allons utiliser les angles afin de positionner des points sur un cercle. Là dessus processing est très bien fait puisqu’il va nous proposer différentes fonctions trigonométriques tel que :

float s = sin(a); //sinus d'un angle
float c = cos(a); //cosinus d'un angle
float t = tan(a); //tangente d'un angle

float as = asin(a); //inverse du sinus
float ac = acos(a); //inverse du cosinus
float at = atan(a); //inverse de la tangente

Une des particularités à savoir sur processing et qu’il effectue ses calculs d’angle en radians là où nous avons plutôt tendance utiliser des dégrés. Le radians est l’unité de mesure internationale d’un angle. Ainsi, si en degrés nous divisons un cercle en 360° en radians celui-ci se divise entre 0 et 2PI

cercle_trigonom_trique

Lorsque nous travaillons en degrés on pourrait se dire qu’il va nous falloir entrer dans des calculs fastidieux mais là aussi processing nous offre une fonction permettant de convertir un angle de degrés en radians

float a = radians(valeur en degré);

Nous avons maintenant tout ce qu’il nous faut pour tester la trigonométrie dans processing.

Attracteur et répulseur simple

Pour voir à quoi peut nous servir la trigonométrie prenons un exemple d’un projet d’un étudiant de 4ème année.
Ce dernier réalisait une installation interactive avec la wiimote. Cette dernière lui servait de pointeur et il souhaitait que régulièrement, les lettres présentent sur le sketch à des positions aléatoires, attaquent le pointeur. Essayons de reproduire cela le plus simplement possible.

Nous allons produire un sketch de 500*500 sur lequel nous placerons des balles à des positions aléatoire. Celles-ci attaqueront notre souris. C’est à dire qu’elles se déplaceront en direction de notre souris et une fois leurs déplacements effectués, retournerons à leurs places avant d’effectuer une autre attaque.

Cela peut sembler compliqué. Comment faire en sorte de rapprocher nos balles vers notre souris sachant que certaines seront plus haute, plus basse, plus à droite ou plus à gauche. Réduisons notre concept à une balle on se rend tout de suite compte qu’il nous suffit d’utiliser la trigonométrie. Nous avons une balle et une cible. Nous connaissons la distance entre ces deux élément et nous savons que la position d’un point sur un cercle est égale à :

  • x = cos(angle)*rayon
  • y = sin(angle)*rayon

Il nous suffit alors de calculer la position de notre balle sur un cercle dont le rayon est égale à sa distance la séparant de sa cible. Nous n’auront plus qu’à incrémenter ou décrémenter notre rayon pour attirer ou repousser notre balle

trigo2

Reproduisons cela à partir d’une balle que nous placerons de maniere aléatoire, nous définirons la position de l’attracteur au centre de notre skecth. La première chose que nous devons faire c’est de replacer cette balle sur un cercle dont le rayons sera égale à la distance entre la balle et sa cible. Pour cela nous allons utiliser la fonction dist() qui nous permettra d’obtenir le rayon de notre cercle. C’est ce rayon que nous incrémenterons puis decrémenterons pour attirer ou repousser notre balle.

d = dist(balle_x, balle_y, cible_x, cible_y);

Nous savons que les coordonnées x, y d’un point sur un cercle sont respectivement égales à cos(angle)*rayon et sin(angle)*rayon. Nous avons déjà le rayon, il ne nous manque donc plus que l’angle.

Si on regarde de plus pres notre schema, nous avons notre balle sur un cercle de centre cible mais nous avons aussi un triangle rectangle dont nous connaissons déjà l’hypothenuse c’est à dire notre rayon.

gabarit_blogeart_3

Il nous est alors facile d’obtenir les autres valeurs puisque nous avons les cordonnées de notre balle et de sa cible. Ainsi :

  • a = balle_x – cible_x
  • b = balle_y – cible_y

Nous savons aussi que la tangente d’un angle est égale au côté opposé / côté adjacent ou dans notre cas à b/a. Pour obtenir notre angle il nous faudra alors obtenir l’inverse de cette tangente. C’est là l’utilité de la fonction atan() de processing qui nous reverra l’inverse de la tangente et donc notre angle en radians

angle = atan2(balle_y - cible_y, balle_x - cible_x);

Nous pouvons donc définir les coordonnées de notre balle sur le cercle comme ceci

  • x = cos(angle)*rayon
  • y = sin(angle)*rayon

Il ne nous reste plus qu’à décrémenter puis incrémenter notre rayons. Nous obtenons alors ceci
(cliquez sur le skecth pour activer l’attracteur ou sur ‘a’ pour repositioner la balle)

//Cliquez pour lancer l'animation
float posX, posY, oX, oY, d, angle, r, v;
boolean etat = true;

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

void draw()
{
  background(255);
  //calcul de l'angle
  angle = atan2(posY - oY, posX - oX);
  //deplacement de la balle
  if (etat == true)
  {
    r=d; //si nous n'activons pas l'attracteur le rayon est égale à la distance entre nos éléments
  }
  if (etat == false)
  {
    r+=v; //sinon notre rayon s'incrémente de la vitesse (-1 donc négative)
  }

  if ( r<1 || r>d)
  {
    v = -v;//si nous atteignons les limite rayon ou 0 notre vitesse s'inverse. Nous attirons puis repoussons notre balle
  }

  //position de la balle
  posX = oX+cos(angle)*r;
  posY = oY+sin(angle)*r;

  //balle
  stroke(0);
  strokeWeight(5);
  point(posX, posY);
  strokeWeight(1); 
  stroke(255, 0, 0);
  line(posX, posY, oX, oY);
  ellipse(posX, posY, 15, 15);

  //cercle trigonométrique
  stroke(0);
  point(oY, oY);
  strokeWeight(1);
  noFill();
  ellipse(oX, oY, d*2, d*2);
  fill(255);
  ellipse(oX, oY, 10, 10);
}

void keyPressed()
{
  if (key == 'a')
  {
    update();
  }
}

void mousePressed()
{
  etat = !etat;  
}

//fonction initialisant notre balle
void update()
{
  oX = width/2;
  oY = width/2;
  posX = random(width);
  posY = random(height);
  d = dist(posX, posY, oX, oY);
  v = -1;
}

ixd_trigo_00

Nous avons une balle attirée par notre cible mais celle-ci est fixe et nous n’avons qu’une balle. Comment appliquer cela à plusieurs balles?

Pour cela rien de plus simple, il nous suffit d’utiliser les listes et de replacer chacune de nos balles sur un cercle dont le rayon sera la distance entre la balle et sa cible. Il nous faut utiliser les notions vu dans le cours précédent à savoir les liste et les boucle for (disponible ici).

Nous obtenons alors ceci (cliquez pour activer l’attracteur, appuyez sur ‘a’ pour repositioner les balles et sur ‘z’ pour retirer les tracés)

//Cliquez pour lancer l'animation

float oX;
float oY;
float nbParticles;
float[] d;
float[] angle;
float[] posX;
float[] posY;
float[] posX_2;
float[] posY_2;
float[] v;
float[] r;

boolean etat = true;
boolean dessin = true;

void setup()
{
  size(500, 500);
  //frameRate(2); 
  update();
}

void draw()
{

  background(255);
  oX = mouseX;
  oY = mouseY;
  for (int i = 0; i<nbParticles; i++)
  {
    angle[i] = atan2(posY[i] - oY, posX[i]  - oX);
    d[i] = dist(posX[i], posY[i], oX, oY);
    //deplacement de la lettre
    if (etat == true)
    {
      r[i]=d[i];
    }
    if (etat == false)
    {
      r[i]+=v[i];
    }

    if ( r[i]<1)
    {
      v[i] = -v[i];
    }
    if ( r[i]>d[i])
    {
      v[i]= -v[i];
      r[i]=d[i];
    }

    //position de la lettre 
    posX_2[i] = oX+cos(angle[i])*r[i];
    posY_2[i] = oY+sin(angle[i])*r[i];

    if(dessin == true)
    {
    float a = map(d[i], 0, width, 0, 50);  
     stroke(0, a);
     ellipse(oX, oY, d[i]*2, d[i]*2);
     //dessin de l'origine de point
    stroke(0);
    strokeWeight(5);
    point(posX[i], posY[i] );
    strokeWeight(1);
    line(posX[i], posY[i], oX, oY);
    stroke(255, 0, 0);
    strokeWeight(1);
    line(posX_2[i], posY_2[i], oX, oY);
    }

    //dessin de lettre
    stroke(255, 0, 0);
    strokeWeight(1);
    ellipse(posX_2[i], posY_2[i], 15, 15);

  }

  //ellipse 
  strokeWeight(1);
  stroke(255, 100);
  point(oY, oY);
  noFill();

}

void keyPressed()
{
  if (key == 'a')
  {
    update();
    etat = true;
  }
  if(key == 'z')
  {
    dessin = !dessin;
  }
}

void mousePressed()
{
  etat = !etat;

}

void update()
{

  nbParticles = 20;
  oX = width/2;
  oY = height/2;
  posX = new float[20];
  posY = new float[20];
  d = new float[20];
  v = new float[20];
  angle = new float[20];
  r = new float[20];
  posX_2 = new float[20];
  posY_2 = new float[20];
  for (int i = 0; i<nbParticles; i++)
  {
    posX[i] = random(width);
    posY[i] = random(height);
    d[i] = dist(posX[i], posY[i], oX, oY);
    v[i] = random(-4, -0.5);
  }
}

ixd_trigo_01

Design génératif et trigonométrie

Nous avons vu comment faire un attracteur/repulseur simple mais allons un peu plus loin dans l’utilisation de ces méthodes et appliquons cela à du design génératif. Il nous est facile à partir de là d’imaginer un visuel génératif type fleur.

Reprenons nos balles. Définissons leur position de départ sur un cercle de rayon 200 et toute équidistantes les une des autres.
Nous gardons notre principe d’attracteur et définissons une vitesse aléatoire pour chacune de nos balles. Au fur et à mesure celle-ci se déplaceront vers le centre de notre skecth. À chaque fois que l’une d’entre elles atteindra le centre nous clôturons le cycle et en déclencherons un nouveau. À chaque début de cycle nous définirons une plage de couleur pour notre fleur générative. Ajoutons un peu de piquant à notre sketch et définissant à chaque debut de cycle un sens de rotation pour notre fleur. Enfin définissons pour chaque cycle un temps de vie de 1000 frame avant de se re-initialiser

trigo3

Globalement nous allons utiliser le même code que notre attracteur à plusieurs balles mais nous ne dessinerons pas la même chose. Commençons par définir une fonction update() dans laquelle nous initialiserons toute nos valeurs.

float oX, oY, d; //cible et distance de la cible
float[] posX; //position des partciules
float[] posY; //position des partciules
color[] c; //couleurs des particules
float[] r; //rayon des particules sur le cercle trigonométrique
float[] angle; //angle des particules sur le cercle trigonométrique
float[] vr; //vitesse des particules
float colorRatio; //plage de couleur

boolean etat = false;//animation on/off
int nbParticules;//notre de point

float angleGlobal; //angle de rotation de notre skecth
float vAngle; //sens de la rotation

float count; //notre décompte. Toute les 500 frame nous effacerons notre fleur pour en dessiner une nouvelle.

void setup()
{
  size(500, 500);
  colorMode(HSB, 360, 100, 100, 100); // ici nous passons en mode colorimétrique Teinte Saturation Luminosité. Ce qui nous pemrettra de définir plus aisement nos plages de couleur
  update();  //fonction initialisant nos valeurs
  background(0);
}

void update()
{
  vAngle = random(-0.1, 0.1); //définition du sens de rotation du sketch 
  colorRatio = random(0, 300); //choix d'un plage de couleur entre 0 et 300° - notre plage sera entre cette valeur et cette valeur+60
  nbParticules = 50; 
  oX = width/2;
  oY = width/2;
  posX = new float[nbParticules];
  posY = new float[nbParticules];
  r = new float[nbParticules];
  angle = new float[nbParticules];
  vr = new float[nbParticules];  
  c = new color[nbParticules];

  for (int i=1; i<nbParticules; i++) { 
    angle[i] = ((2*PI)/nbParticules)*i; //Ici nous divisons le cercle 2PI par le nombre de particule multiplier par i pour obtenir l'angle de chaque particule et les placer à équidistance les une des autres
    vr[i] = random(-0.5, -0.1); 
    r[i] = 200; //ici nos particules ont un rayon de départ identique pour former notre cercle mais nous pouvons aussi le définir en random et obtenir un autre visuel random(150, 200);
    posX[i] = oX+cos(angle[i])*r[i]; //position x sur le cercle x = cos(angle)*rayon
    posY[i] = oY+sin(angle[i])*r[i]; //position y sur le cercle y = cos(angle)*rayon
    d = dist(posX[i], posY[i], oX, oY); //distance entre les particules et la cible
    c[i] = color(random(colorRatio, colorRatio+60),random(80, 100), random(80,100)); //définissions de la couleur
  }
}

Maintenant que nous avons notre cercle de départ il ne nous reste plus qu’à le dessiner de la manière suivante

void draw()
{

  pushMatrix();
  /*rotation de la fleur*/
  if (etat == false) //si nous activons la rotation de la fleur
  {

    angleGlobal += vAngle;
  }
  else if (etat == true) //sinon la fleur reste droite
  {
    angleGlobal = angleGlobal;
  }
  translate(oX, oY);
  rotate(radians(angleGlobal));

  /*calcul des nouvelles positions de notre fleur*/
  for (int i=1; i<nbParticules; i++) { 
    r[i] += vr[i]; // reduction du rayon

    if (r[i]>200 || r[i]<0) //limite de la fleur. Si une des particules atteint le centre alors nous re initinalisons un cycle à l'aide de la fonction update();
    {
      update();
    }
    //position des partciules
    posX[i] = cos(angle[i])*r[i];
    posY[i] = sin(angle[i])*r[i];
    stroke(c[i], 5);
    noFill();
   //dessin de la fleur
    if (i != nbParticules-1)
    {
      line(posX[i], posY[i], posX[i+1], posY[i+1]);
    }
    else
    {
      line(posX[i], posY[i], posX[1], posY[1]);
    }
  }
  popMatrix();
  //decompte
  count++;
  if(count >= 1000)
  {
    count = 0;
    background(0);
  }
}

void keyPressed()
{
  if (key == 'a')
  {
    etat = !etat;
  }
}

//interactivité
void mousePressed()
{
  background(0);
  update();
}

Nous obtenons alors la fleur suivante. À partir de là il nous est facile de rajouter des comportements à notre fleur ou de changer sa forme de départ, son sens de rotation, la manière dont elle se dessine… et tout ça sur une base de trigonométrie.
trigonometrie

Les Arrays… premier pas vers les objets

Les tableaux, listes ou arrays font partis des fondamentaux des langages de programmation. Il s’agit d’une «variable» nous permettant de stocker plusieurs variables auxquelles on aura accès via un numero d’index. Sur le papier cela peut faire peur mais par un exemple c’est tout de suite plus simple.

Prenons une classe d’étudiants, chaque étudiant a un prénom. Nous avons donc un tableau d’étudiant de ce type

String nom de l'étudiant = {Pierre, Paul, Jacques};

Je pourrai donc alors dire que le nom de l’étudiant 1 = Pierre, le nom de l’étudiant 2 = Paul et le nom de l’étudiant 3 = Jacques

À quoi ça sert?

En premier lieux à nous simplifier la vie. C’est le premier pas avant le développement orienté objet. Les tableaux vont nous permettre de stocker des valeurs « communes » pour les traiter plus facilement et par groupe.

Imaginons que nous voulions faire un sketch de 100*100 avec 3 balles sur scène. Nous aimerions qu’en fonction des touches du clavier a, z ou e cela change la couleur de la balle 1, 2 ou 3 et restore la couleur des autres balles à leur valeur d’origine. Pour cela, sans tableau nous aurions écris :

float c1=255;
float c2=255;
float c3=255;

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

void draw()
{
  background(255);
  ellipseMode(CORNER);
  fill(c1);
  ellipse(0, 0, 10, 10);
  fill(c2);  
  ellipse(0, 10, 10, 10);
  fill(c3);
  ellipse(0, 20, 10, 10);
}

void keyReleased()
{
  if (key == 'a')
  {
    c1 = 0; 
    c2 = 255;
    c3 = 255;
  }  
  if (key == 'z')
  {
    c2 = 0;
    c1 = 255;
    c3 = 255;
  }  
  if (key == 'e')
  {
    c3 = 0;
    c1 = 255;
    c2 = 255;
  }
}

ixd_array_00-01

Le résultat fonctionne mais si nous voulons appliquer cela à 10, 50, ou 200 balles alors cela devient très vite laborieux d’écrire 10, 50 ou 200 variables. C’est là où nos tableaux deviennent plus pratiques.

Utilisation d’un tableau

Comme nous l’avons vu plus haut un tableau est une variable, nous verrons donc qu’il n’est pas très différent dans sa déclaration. Quand à son utilisation, nous verrons aussi qu’il nous faudra utiliser notre vieille amie, la boucle for().

Un tableau se déclare assez facilement, très proche de la variable, nous devrons utiliser les [] pour déclarer notre tableau puis les {} pour en déclarer des éléments. Par exemple, pour déclarer les lettres avec lesquelles nous pourrons changer de couleurs, nous écrirons

char[] l = {'a', 'z', 'e'};

Dans le cas où nous voudrions déclarer des éléments identiques dans notre tableau, ou de façon dynamique, notre syntaxe changera. Il nous faudra toujours déclarer notre tableau avec nos [] mais nous déclarons les éléments dans une boucle for(). Cela nous facilite la vie, comme dans le cas de nos balles où nous voulons qu’elles soient toute blanche dès les départ.

flaot[] c = new float[3]; // ici nous déclarons un tableau c de 3 éléments

for(int i=0; i<3; i++) // puis nous définisons chaque element comme un valeur de 255;
  {
    c[i] = 255;    
  }

Nous avons donc créé deux tableaux, nous allons maintenant les appliquer à notre Sketch de 100*100. Commençons par dessiner nos ellipses et définir leur remplissage. Dans mon premier skecth, nous avions écris 3 valeurs fill() et 3 ellipses, ce qui était assez laborieux. Maintenant toute ces lignes peuvent se résumer comme il suit :

for(int i = 0; i<c.length; i++) // Notons que la méthode .length nous renvoie la taille de notre tableau. Ici c.lenght = 3 car notre tableau contient 3 éléments.
  {
    ellipseMode(CORNER);
    fill(c[i]);
    ellipse(0, i*10, 10, 10);
  }

Appliquons maintenant cette même méthode à la partie interactive. La seule différence ici c’est qu’il va nous falloir savoir sur quelle touche nous avons appuyé. Précédemment nous utilisions des condition if(key == ‘a’). Nous allons faire la même chose mais de manière dynamique à l’aide de notre boucle for

void keyReleased()
{
 for(int i = 0; i<c.length; i++)
 {
  if (key == l[i])
  {
    c[i] = 0;
  }
  else
  {
    c[i] = 255;
  }
 }
}

Nous obtenons alors le même résultat pour moins de ligne et surtout en étant plus flexible. Il nous sera facile de créer 100 balles et les rendre interactive.
ixd_array_00-01

Application à des random walker?

Nous avons vu comment faire des tableaux sur 3 balles mais allons plus loin en appliquant ces nouvelles connaissances à des random walkers.

Un random walker ou marcheur aléatoire est un objet, ici un cercle, se déplaçant de manière aléatoire. Imaginons le sketch suivant :

Nous aimerions avoir plusieurs cercles (50), effectuant un déplacement du bas vers le haut à une vitesse aléatoire et un déplacement latéral aléatoire entre -1 et 1 pixel. Nous aimerions aussi que nos cercles aient une taille aléatoire et que cette même taille varie de manière aléatoire. Enfin nous aimerions que le contour de nos cercles soit plus ou moins noir en fonction de leur distance avec la souris.

Si nous étudions un peu ce que nous devons faire nous nous rendons compte qu’il nous faut des valeurs différentes mais commune à tous tel que :

  • La position en x d’un cercles
  • La position en y d’un cercles
  • La taille minimale d’un cercle
  • La taille maximale d’un cercle
  • la vitesse en y d’un cercle

Nous avons donc besoins de 5 tableaux. Commençons par déclarer nos tableaux et le nombre de nos particules

float[] x;
float[] y;
float[] taille1;
float[] taille2;
float[] vy;
int nbParticules;

Nos tableaux étant créés il va nous falloir les remplir. Là encore regardons ce que nous voulons faire.
Nous voulons que nos cercles soit placés les un à coté des autres en x. Cela veut donc dire que chaque cercle sera placé à équidistance les un des autres au départ.Nous savons aussi qu’au départ ils seront tous positionnés en bas de notre skecth. Enfin nous savons qu’ils auront une vitesse aléatoire et des tailles variantes et aléatoires entre eux. Nous aurons donc dans notre setup()

void setup()
{
  size(700, 500, JAVA2D);
  nbParticules = 50; //nb de particules
  x = new float[nbParticules];
  y = new float[nbParticules];
  taille1 = new float[nbParticules];
  taille2 = new float[nbParticules];
  vy = new float[nbParticules];

  for (int i=0; i<nbParticules; i++)
  {
      x[i] = width/nbParticules*i; //position les cercle tout les 14 pixels
      y[i] = height; //placer les cercles en bas du skecth
      taille1[i] = random(0, 5); //definir une taille minimal aléatoire
      taille2[i] = random(10, 20); //définir une taille maximal aléatoire
      vy[i] = random(0.2, 1); //définir une vitesse aléatoire
   }
   background(255);
}

Il nous faut maintenant dessiner nos cercles. Là encore regardons ce que nous voulons faire.
Nous aimerions que la couleur de contour de nos cercles dépendent de leur distance avec la souris. Pour cela attardons nous un peu sur deux fonctions de processing dist() et map().

Pour réaliser cela nous avons besoins de connaitre la distance entre la souris et un cercle. On pourrait entrer dans des calculs plus ou moins complexe avec des x et des y ou nous pouvons profiter d’une des fonctions de processing nous permettant de calculer la distance entre deux points. Pour cela il nous suffit d’appeler la méthode dist().

float d = dist(x1, y1, x2, y2); //notons que cette methode peut aussi calculer des distance dans un espace 3D

Nous obtenons alors une distance comprise entre 0 et 860 pixels environs (diagonale du skecth).
On pourrait dire que le contour de notre cercle est égale à cette valeur mais nous savons que le mode de colorimétrie de processing fonctionne sur des valeur RVB comprissent entre 0 et 255. Il nous faut donc appliquer une règle de proportionnalité afin de dire que les valeur 0-255 de notre contour sont proportionnelles à la distance d. Une fois de plus processing nous propose une méthode très simple nous permettant de réaliser cette règle de 3, la méthode map().

float m = map(valeur à mapper, minimale de départ, maximale de départ, minimale d'arrivée et maximale d'arrivée);

Maintenant que nous savons comment faire varier notre couleur de contour essayons de déplacer nos cercles. Nous savons qu’ils doivent de déplacer en latérale de manière aléatoire entre -1 et 1. il nous faut donc la valeur x suivante

x = random(-1, 1);

Pour le mouvement vertical, nos cercles doivent se déplacer à des vitesses différentes et oscillent entre le haut et le bas. Nous allons donc incrémenter notre y de la valeur de notre vitesse et inverser cette valeur lorsque y attendra ses limites haute et basse

y -= vy;

    if(y < 0 || y > height)
    {
      vy *= -1;
    }

Nous avons alors tout ce dont nous avons besoins pour la boucle for() de notre draw.

for (int i=0; i<nbParticules; i++)
  {
    float d = dist(mouseX, mouseY, x[i], y[i]);
    float c = map(d, 0, width, 0, 255);

    float taille = random(taille1[i], taille2[i]);

    x[i] += random(-1, 1);
    y[i] -= vy[i];

    if(y[i] < 0 || y[i] > height)
    {
      vy[i] *= -1;
    }

    stroke(random(c));
    fill(255);
    ellipse(x[i], y[i], taille, taille);
  }

Si nous observons notre code dans la globalité et le résultat nous obtenons alors

float[] x;
float[] y;
float[] taille1;
float[] taille2;
float[] vy;
int nbParticules;

void setup()
{
  size(700, 500, JAVA2D);
  nbParticules = 50;
  x = new float[nbParticules];
  y = new float[nbParticules];
  taille1 = new float[nbParticules];
  taille2 = new float[nbParticules];
  vy = new float[nbParticules];

  for (int i=0; i<nbParticules; i++)
  {
      x[i] = width/nbParticules*i;
      y[i] = height;
      taille1[i] = random(0, 5);
      taille2[i] = random(10, 20);
      vy[i] = random(0.2, 1);
   }
   background(255);
}

void draw()
{

  for (int i=0; i<nbParticules; i++)
  {
    float d = dist(mouseX, mouseY, x[i], y[i]);
    float c = map(d, 0, width, 0, 255);

    float taille = random(taille1[i], taille2[i]);

    x[i] += random(-1, 1);
    y[i] -= vy[i];

    if(y[i] < 0 || y[i] > height)
    {
      vy[i] *= -1;
    }

    stroke(random(c));
    fill(255);
    ellipse(x[i], y[i], taille, taille);
  }
}

 Array

Encore plus de possibilités dans le browser

Les développements récents de HTML5 rend le browser de plus en plus puissant comme outil de prototypage [voir de développement] de réalisations qui auparavent devaient se faire en Flash ou par le biais d’un sketch Prototype.

Voici donc un lien vers une expérimentation Move a Cube With Your Head or Head-Tracking With WebGL — Bouger un cube avec votre tête, ou le suivi de visage avec WebGL — qui fait une démonstration, à la fois des librairies récentes de JS [ici, three.js] et les ouvertures du browser vers les dispositifs d’entrée de son ordinateur.

Interactive Cover, à la découverte de l’aléatoire [Rendu]

Après plusieurs cours d’initiation à Processing et après le cours sur la gestion du bruit Brownien et bruit Perlin, les étudiants de 3ème années de l’option digital lab ont été amené à réaliser leur première experimentation interactive sur écran à l’aide de Processing.

Le Brief & les contraintes

« Pour annoncer la sortie de leur nouvel album, un groupe de musique vous demande de réaliser une animation interactive de leur cover. »

Les étudiants, par groupe de 2 à 5 personnes, devront choisir un groupe de musique (fictif ou non) et réaliser, à l’aide de processing, une animation interactive.

Celle-ci devra comporter au moins une des deux notion d’aléatoire vu en cours (bruit brownien ou bruit perlin) ainsi que 1 interaction souris ou clavier.

Le sketch ne pourra comporter aucune autre image que le logo du groupe. Le reste du visuel devant être réalisé en utilisant les techniques de design génératif vues en cours.

Temps de production : 2 cours.

Rendus

Aléatoire, bruit Brownien, bruit Perlin et bruit Gaussien

La notion d’aléatoire ou de hasard à une grande place dans le domaine du design generatif et de l’experience interactive. Nous avons régulièrement besoins dans de nombreux cas de générer une ou des valeurs aléatoires. Par exemple, si nous désirons recréer le déplacement chaotique d’un insecte volant, il peut être très pratique de définir sa vitesse ou son déplacement de manière aléatoire. Nous ne serons pas dans une reproduction parfaite de la nature mais nous aurons créé un objet qui aura ses propres propriétés qui ont pour particularité d’être aléatoire.

Si nous pouvons utiliser divers type d’aléatoire dans Processing (brownien, gaussien ou perlin) nous ne verrons ici que les deux principaux types d’aléatoire à savoir le bruit brownien et le bruit perlin.

Lorsque nous souhaitons obtenir une valeur aléatoire en code, il nous faut toujours définir une valeur minimum et un valeur maximum. Imaginons ici que nous souhaitons obtenir une valeur aléatoire comprise entre 0 et 100

Le bruit brownien

Le bruit brownien est un aléatoire où les valeurs produites sont réparties de manière totalement aléatoire. Chaque valeur pourra être grandement espacée les une des autres ou totalement proche voir identique. Une répartition brownien peut être imaginé par ce schema :

01_01_aleatoire

Dans processing le bruit brownien est produit par la fonction :

random(valeurMin, valeurMax);

Il nous suffira de définir les valeurs entre parenthèses pour attribuer une valeur minimum ou maximum. Dans notre cas nous voulions obtenir une valeur comprise entre 0 et 100.
Cela nous donne donc :

random(0, 100);

Appliquons cela dans processing en créant un skecth de 500*500 où nous dessinerons une ellipse de 10*10 à une position aléatoire définie par un random.
Cela nous donne donc :

float x;
float y;

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

void draw()
{
  background(0);
  x= random(0, 500);
  y = random(0, 500);
  ellipse(x, y, 20, 20);
}

ixd_random_00

La position de notre ellipse est donc aléatoire et celle-ci semble se déplacer comme un objet se téléportant d’un point à un autre.

Le bruit Perlin

Le bruit perlin est bien différent du bruit brownien tant dans sa syntaxe que dans les valeurs qu’il renvoie. Contrairement au bruit brownien le bruit perlin produit une séquence de valeur où chaque valeur est proche de la valeur précédente. On obtient donc un répartition harmonieuse de valeur. La structure de bruit réel est similaire à celle d’un signal audio, en ce qui concerne l’utilisation de la fonction de fréquences. Similaire à la notion d’harmoniques en physique, bruit de Perlin est calculée sur plusieurs octaves qui sont additionnés pour le résultat final.

On peut illustrer le bruit perlin par ce schema

01_02_aleatoire

Ce bruit est très utilisé dans la génération de texture procédural mais aussi dans les déplacement de particule puisqu’il nous permet d’avoir une harmonie entre les valeurs et donc des sensations d’incrémentation et décrémentation plus douces.

Dans sa syntaxe le bruit perlin est bien différent du bruit brownien.
voici comment il se déclare :

noise(valeur);
//ou
noise(valeur, valeur);
//ou
noise(valeur, valeur, valeur);

On remarque de prime abord que là où le bruit brownien accueille deux valeurs entre ses parenthèses (minimum et maximum) le bruit perlin peut en accueillir trois. En effet on dis du bruit perlin qu’il peut être uni, bi ou tridimensionnel. Nous nous attarderons sur le bruit perlin uni-dimensionnel dans cette partie.

La seconde remarque que nous ferons sur le bruit perlin est qu’il ne nous renverra toujours qu’une seule valeur comprise entre 0 et 1 et celle-ci sera toujours identique. En effet la répartition de bruit perlin est définie par la valeur précédente obtenue. Il faudra donc incrémenter notre valeurs puis recalculer notre noise afin d’obtenir une suite de valeurs répartie de façon harmonieuse.
Pour cela nous allons utiliser un valeur que nous appellerons xInc et qui sera égale à 0. Nous calculerons un noise à partir de cette valeur puis à la fin de chaque boucle nous l’incrémenterons de 0.01 pour calculer un nouveau noise à partir de la valeur précédente.

float xInc = 0.0;
float x;
x = noise(xInc);
xInc+= 0.01;

Nous obtenons donc une suite de valeurs harmonieuses dépendante de la valeur précédente. Cependant nos valeurs restent comprise entre 0 et 1. Pour obtenir nos valeurs entre 0 et 100 il faudra donc effectuer une règle de trois (ou règle de proportionnalité, voir ici) qui nous renverra nos valeurs sur une échelle de 1 à 100.

Pour cela processing nous offre une fonction toute faite : map(). Cette fonction va nous permettre de mapper nos valeurs comprises entre 0 et 1 entre 0 et 100 de la manière suivante :

x = map(variable à mapper, valeur Min d'origine, valeur Max, d'Origine, valeur Min Finale, valeur Max Finale);

Voyons donc comment obtenir nos valeurs en aléatoire perlin compris entre 0 et 100 :

float xInc = 0.0;
float x;
x = noise(xInc);
x = map(x, 0, 1, 0, 100);
xInc+= 0.01;

Nous voila donc avec nos valeurs comprises entre 0 et 100 et réparties de façon harmonieuse. Appliquons maintenant cette méthode à notre sketch de 500*500 où nous dessinerons une ellipse de 10*10 à une position aléatoire définie par un noise.

float x;
float y;
float xInc = 0.0;
float yInc = 0.0;

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

void draw()
{
  background(0);
  x= noise(xInc);
  y = noise(yInc);
  x = map(x, 0, 1, 0, 500);
  y = map(y, 0, 1, 0, 500);
  xInc += 0.01
  yInc += 0.01
  ellipse(x, y, 20, 20);
}

ixd_random_01

Nous obtenons alors une ellipse dont la position est définie de manière aléatoire en fonction de la position précédente. Cela à pour effet de donner l’impression que notre ellipse se déplace de manière aléatoire comme un insecte par exemple.

Le bruit gaussien

 

Le bruit gaussien, contrairement au brownien et au perlin, se base sur un répartition moyenne des valeurs en fonction de la courbe de Gauss, c’est à dire que la probabilité d’observer une valeur aléatoire s’écartant de la valeur moyenne décroît rapidement en fonction de la valeur de deviation.

On peut illustrer le bruit gaussien par ce schema

gaussian

Afin de définir une valeur aléatoire gaussien dans processing nous avons besoins de définir 3 valeurs :

  1. La valeur gaussienne définie par la méthode
    randomGaussian();
  2. La valeur médiane qui définie la valeur moyenne que nous souhaitons obtenir.
  3. La valeur de déviation qui définie la distance maximum qui sépare la valeur médiane de la valeur des extrêmes (min/max)

Ainsi pour définir une valeur gaussienne comprise la médiane est 50 et dont la déviation (min/max) est 25 nous aurons

float gaussianNumber = randomGaussian();
float median = 50;
float deviation = 25;
float gaussianDistributedNumber = gaussianNumer * deviation + median;

L’exemple suivant montre comment dessiner une ellipse dont la position est définie par une distribution gaussienne.

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

void draw()
{
  float randomGaussianX = randomGaussian(); //Define a random value with a deviation of 1 and a median value of 0
  float medianX = width/2; //Define the Media value
  float deviationX = 25;//define the deviation (min/max)
  
  float randomGaussianY = randomGaussian(); //Define a random value with a deviation of 1 and a median value of 0
  float medianY = height/2; //Define the Media value
  float deviationY = 25;//define the deviation (min/max)
  
  float x = (randomGaussianX * deviationX) + medianX;
  float y = (randomGaussianY * deviationY) + medianY;
  
  fill(255, 50);
  noStroke();
  ellipse(x, y, 10, 10);
  
}

gaussian

Introduction à Processing [1.5]

Processing est un langage simplifié de JAVA créé par Benjamin Fry et Casey Reas au MIT Media Lab. Leur projet était de créer un langage simple, destiné aux graphistes et artistes, afin de répondre à certains de leur besoins tel que la visualisation de données ou la production de visuels génératifs. Processing est le prolongement « multimédia » de Design by numbers, l’environnement de programmation graphique développé par John Maeda au MIT Media Lab.

Processing a bien évolué depuis sa création en 2001 et est devenu un véritable outils de production pour les designer interactif et artistes.

Aujourd’hui à sa version beta 2.08 processing intègre un grand nombre de librairies créées par une communauté active et s’exporte désormais aussi en web par son extension processing.js.

Si nous devions faire un rapide état des lieux des principaux langages et/ou frameworks utilisés dans le design génératif ou l’art numérique nous aurions :

  • C/C++ avec des framework tel que OpenFramework
  • Java en natif ou simplifié avec Processing ou Eclipse
  • Les langages de type nodal avec VVVV ou Max MSP/Jitter

Il existe évidement de nombreux autres langages utilisés dans l’expérimentation interactive et visuel tel que Flash ou Javascript.

Les cours d’option « Digital Lab » à e-art sup Paris porteront sur les expérimentations visuelles et interactives, qu’elles soient sur écran ou hors écran et ce à travers l’utilisation de Processing. Cette option à pour but d’expérimenter de nouvelles techniques de design apporter par le design par le code et de sortir des techniques habituelles de conception. Il sera une initiation à l’expérimentation interactive et aux cours de développement interactif apportés par la filière Design Interactif d’e-art sup.

Enfin, processing 2.0 étant encore en version beta, l’essentiel des cours porteront sur Processing 1.5, la version stable de Processing.

Processing et son environnement

En tant que Graphiste, l’interface de processing peut paraitre un peu « aride » lors de son premier lancement. En effet nous sommes tous des habitués de photoshop et ce qui se rapproche le plus du code pour nous reste flash avec son interface proche du tableau de bord d’une formule 1. Et comme pour photoshop ou la formule 1 il va nous falloir comprendre l’interface avant de se lancer dans la production.

L’interface de processing peut se découper en trois zones :

00_01_processing_interface

La première, que nous appellerons « header » contient les principaux éléments d’interface à savoir les boutons permettant :

  • 1 : l’execution du code
  • 2 : Stopper l’execution du code
  • 3 : Créer un nouveau sketch
  • 4 : Ouvrir un sketch
  • 5 : Sauvegarder un sketch
  • 6 : Exporter un Sketch (en applet web ou en stand alone)
  • 7 : Sélection du mode (JAVA ou Android pour la 1.5, la version 2 nous offre de nouveau mode tel que le JAVASCRIPT)
  • 8 : Créer une nouvelle tab, ou nouvelle feuille de code pour ce même sketch.

La seconde zone est notre zone de code. C’est ici que nous écrivons notre sketch.

Enfin la troisième zone est notre console de debug. C’est dans cette zone que s’affichera toute les données que nous demanderons d’afficher afin de debuguer notre sketch. Cette zone permet d’obtenir des informations textuelles sur un sketch sans que celles-ci soient affichées sur notre création.

Lorsque nous faisons « Executer le Code » ou ctrl+R (cmd+R pour OSX) une nouvelle fenêtre s’affiche. C’est notre fenêtre d’exécution. C’est ici que notre code s’exécute et affiche sa traduction graphique.

Maintenant que nous avons vu l’interface nous reste à faire un tour dans la façon dont processing fonctionne. Nous n’avons fasse à nous qu’une simple zone d’écriture, or nous voulons dessiner.

Il faut donc avant tout comprendre comment processing fonctionne afin de pouvoir traduire nos visuels en design par le code.

Si nous devions faire un rapprochement avec un des logiciels que nous connaissons en graphisme alors ce serait Illustrator. En effet, tout comme illustrator, il faut savoir que processing est un environnement de dessin vectoriel.

Et tout comme illustrator (et les autres logiciels de la suite adobe) Processing possède un système de cordonnées cartésien qui nous permet de situer un point dans l’espace (x, y, z).

Enfin, tout comme les logiciels que nous connaissons en design, l’origine de ce repère cartésien se trouvera en haut à gauche de notre fenêtre d’exécution.

00_02_processing_repere

Processing : les bases

Dans son écriture, un sketch processing s’articule principalement entre deux fonctions.

La première : setup(), est la fonction constructeur. Cette fonction, appelée une seule fois lors du lancement du sketch permettra de définir la taille de notre skecth, ses propriétés de rendu, de frame rate, de position….

La seconde : draw() est la boucle, c’est dans cette fonction, qui se rappelle à chaque fois que la précédente est terminée, que nous dessinerons. Par défaut processing s’exécute avec un frame rate de 25, c’est à dire que en 1 seconde la boucle draw() se sera effectuée 25 fois.

Tout langage de programmation repose sur des notions communes à tous, tous se rapportant aux mathématiques. Il est important de maitriser ces notions de bases afin de pouvoir faire ce que l’on veut de processing et non l’inverse.

La première notion de base et la notion de variables commune à tout les langages. Une variable est un « mot clé » associé à une valeur. Cela permet de stocker en mémoire des valeurs sous un nom.

Par exemple si je place deux points de cordonnées 10, 10 et 20, 20 il me sera difficile en code de changer les coordonées de mon deuxieme point car je ne sais pas comment les appeler. Or, si je dis que mon premier point est placé en coordonnée x1, y2 et le seconde x2, y2 et que je défini par la suite x1 = 10 , y1= 10 et x2 = 20, y2 = 20 il m’est alors facile de changer la valeur de x1 en 30.

Il existe plusieurs types de variables dans processing mais nous ne commencerons ici que par les principales. Nous avons donc :

  • boolean etat = true;  Variable de type boolean ou binaire, il s’agit d’une variable ne pouvant renvoyer que deux valeurs : Vrai/Faux. Elle permet de faire des comparaisons par exemple « Cette robe est-elle rouge? » est un question demandant un réponse booleenne. « Oui elle est rouge » « non elle ne l’est pas »
  • int x = 10;  est une variable renvoyant un nombre entier
  • float x = 3.141592;  est une variable renvoyant un nombre décimal
  • String nom = « moi »; est une variable renvoyant une chaine de caractères, elle permet de stocker des mots.
  • char nom = « a »; est une variable ne pouvant stocker que un caractère.

La seconde notion commune est la notion d’opérateurs. Ils permettent d’effectuer divers calculs ou comparaisons. Nous sommes ici dans des notions de bases des mathématiques.

Nous avons différents types d’opérateurs.

  • +, , *, / qui permettent d’addition, soustraire, multiplier ou diviser des valeurs.
  • % le modulo qui permet de connaitre la valeur d’un reste d’une division (valeur résiduelle) par exemple 17%3 = 2 car 2 est le reste de la division de 17/3

Les opérateurs d’assignation :

  • = permet d’attribuer une valeur à un variable
  • +=, -=, *=, /= permettent d’incrémenter, soustraire, multiplier ou diviser des valeurs par exemple x = x+1 est la même chose que x +=1;

Les opérateurs relationnels

  • >, <, <=, =>, == , != permettent de comparer deux valeurs afin de savoir si celles-ci sont supérieur, inférieur, supérieur ou égale, inférieur ou égale, égale ou différentes. Ces opérateur ne renvoi que des valeurs booléennes, c’est à dire vrai ou faux. Par exemple  1 == 2 renverra la valeur « false » car 1 n’est pas égale à 2;

Les opérateurs logiques

  • &&, || permettent d’effectuer des opérations booléenne de type ET et OU.

Enfin les dernières notions communes aux trois quart des langages et indispensable sont les structures conditionnelles et itératives.

Les structures conditionnelles permettent de comparer des valeur de d’attribuer des conditions.

Par exemple j’aimerai pouvoir marcher que si mes lacets sont fait sinon je tombe pourrai s’écrire :

boolean lacetsFait = true; //ici mes lacets sont fait, si lacetFait = false alors il ne sont pas fait

boolean tombe; //ici la variable booléenne n'est pas encore définie car je ne sais pas encore si je vais tomber ou pas

if(lacetFait == true)
{
tombe = false;
}
else{
tombe = true;
}

En mathématique, un itération est l’action de répéter un processus. La structure itérative permet d’effectuer une suite d’opération. Très utiliser pour effectuer plusieurs actions simultanées.

Par exemple, si je désire dessiner des lignes verticales de 100 pixels de haut et ce tout les 10 pixels de large je pourrais très bien écrire :

line(0, 0, 0, 100);
line(10, 0, 10, 100);
line(20, 0, 20, 100);
line(30, 0, 30, 100);
line(40, 0, 40, 100);
…

Et ce pour autant de largeur que je veux pour mon dessins. C’est assez simple pour un dessin de 100 pixel de large mais que ce passe-t-il pour 1920 pixels de large.

Je ne vais pas écrire 1920/10, soit 192 fois la même ligne? C’est là où la structure itérative entre en action.

Elle va me permette de dire que pour une variable i égale 0 (mon départ); i toujours inférieur à 1920 (la limite de mon dessin) et i s’incrémentant de 10 pixels (cad pour i=0 puis 10, puis 20…)

alors je dessine mes lignes. Ce qui donne

for(int i = 0; i<1920; i+=10)
{
line(i, 0, i, 100);
}

Et me voila avec un dessin de 192 lignes de 100 pixels de haut espacées de 10 pixels entre elles. Bref l’itération est notre amie.

Nous venons donc de voir les bases communes aux trois quart des langages de programmation, il est grand temps d’entrer dans le vif du sujet avec les bases de processing.

Comme nous avons vu au début, processing est tirédu JAVA, il s’agit d’une version simplifiée de ce langage. Il nous offre donc plusieurs fonctions permettant de dessiner en quelques secondes.

Les deux premières fonctions que nous allons voir permettent de définir la taille de notre sketch, son moteur de rendu et son antialiasing.

Ces deux fonctions seront appelées dans le setup() de notre sketch.

Nous savons que processing est un environnement vectoriel mais de base il va tenter d’économiser un maximum de mémoire. C’est la raison pour laquelle il possède différents moteurs de rendu permettant de dessiner avec plus ou moins de lissage ou seulement en 2D ou avec de la 3D. Processing 1.5 possède 4 moteurs de rendus :

P2D – moteur de rendu de processing pour des dessins 2D – demande peu de ressources mais le lissage reste de basse qualité.

JAVA2D – moteur de rendu 2D de JAVA avec un grande qualité de lissage.

P3D – moteur de rendu 3D de processing, peu gourmand en ressources mais les lissage n’est pas au rendez vous

OPENGL – Moteur de rendu 3D utilisant OPENGL, le calcul est beaucoup plus rapide. (ce dernier nécessitera l’ajout d’une librairie incluse dans processing. Cette librairie ne sera plus utile dans la version 2 de processing.)

Même si nous avons appelé un moteur de rendu performant (par exemple JAVA2D) nous nous rendrons compte que notre ligne reste pixelisée. Cela parce que nous devrons appeler l’antialiasing afin de lisser notre dessin.

Voici donc comment ces fonctions vont s’écrire :

size(largeur, hauteur, moteur de rendu); //permet de définir la taille du sketch et son moteur de rendu, par exemple size(100, 200, JAVA2D);

smooth(); //permet d'activer l'antialiasing

Les prochaines fonctions que nous verrons sont des fonctions de dessins.

background(valeur, valeur, valeur) permet de définir la couleur de notre fond.

Attardons-nous un peu sur cette fonction.Celle-ci est assez particulière car il s’agit d’une fonction permettant de libérer de la mémoire. Nous le savons, notre code se lit successivement en boucle. Cela veut dire que processing va dessiner 25 fois par seconde. Imaginons un sketch de 710*200 où nous dessinerons un cercle de taille 20 à la position de la souris et ce sans background :

void setup()
{
size(710, 200, JAVA2D);
smooth();
}

void draw()
{
ellipse(mouseX, mouseY, 20, 20);
}

ixd_processingIntro_00

lors de l’exécution nous remarquons qu’au fur est à mesure les cercles que nous dessinons restent sur scène et forment une trainée. Nous savons que processing exécute la boucle draw 25 par seconde, or cette boucle dit de dessiner un cercle à la position de la souris. Il est donc logique que en 1 seconde j’ai 25 cercles, en 2seconde 50 et ainsi de suite…. Au bout d’un moment notre ordinateur va se mettre à chauffer et c’est normal car nous demandons pas mal de calculs.

C’est là que la fonction background entre en fonction. En insérant un background (noir par exemple) dans mon draw, je demande a processing d’effacer tout les cercle précédents sur scène pour n’afficher que celui de la boucle actuelle. Cela aura pour effet de nettoyer la mémoire de processing.
Cela nous donnera :

ixd_processingIntro_01

Pour écrire un background dans ma boucle draw j’utiliserai la syntaxe suivante :

background(valeur, valeur, valeur);

Attardons nous un peu sur ces « valeurs » entre parenthèse. Processing me demande 3 valeurs pour définir la couleur de mon fond. Par défaut processing fonctionne en RVB, c’est à dire qu’il chaque couleurs est définie par ces valeurs rouge, verte et bleu, chacune d’entre-elles étant définie entre 0 et 255.

Ainsi background(0, 0, 0) sera noir alors que background(255, 0, 0) sera rouge.

Il faut aussi savoir que processing permet de simplifier son code. Ainsi si nous mettons background(0) processing comprendra que nos 3 valeurs seront égales à 0

Entrons maintenant plus en détail dans les outils de dessin. Processing va nous permettre de dessiner ce que l’on veut mais il nous offre aussi des fonctions pour dessiner des formes géométriques simples. Ainsi nous avons :

point(x, y); //qui permet de dessiner un point

rect(x, y, largeur, hauteur); //permettant de dessiner un rectangle

ellipse(x, y, largeur, hauteur); //permettant de dessiner une ellipse

triangle(x1, y1, x2, y2, x3, y3); //permettant de dessiner un triangle.

Enfin, pour terminer ce chapitre d’initiation sur processing regardons un peu les deux éléments de base qu’il nous manque pour faire notre premier dessin.

Nous avons vu les notions de base communes aux trois quarts des langages, nous avons vu les deux fonctions de base de processing setup() et draw() permettant de définir notre skecth et de dessiner à l’intérieur. Nous avons aussi vu comment définir la taille de notre sketch, son moteur de rendu, son antiliasing et comment dessiner un fond et des formes simples. Autrement dis il ne nous manque que de la mise en forme de ces dessins, à savoir comment définir leurs couleurs de fond et/ou de contour.

Pour cela processing nous met à disposition deux fonctions :

fill(valeur, valeur, valeur); //permet de définir la couleur de fond d'un objet en RVB, cette fonction doit être écrite avant la forme.

stroke(valeur, valeur, valeur); permet de définir la couleur de contour d'un objet en RVB, cette fonction doit être écrite avant la forme.

Mais que ce passe-t-il dans le cas où je veuille avoir un fond ou un contour avec de l’alpha? Qu’à cela ne tienne, processing à tous prévu. Il nous suffira d’ajouter une quatrième valeur à notre fill() ou notre stroke. Nous aurons ainsi défini des valeurs en RVB+Alpha. Mais n’oublions pas une chose, processing fonctionne sur des valeurs RGB de 0 à 255, il en va de même pour l’alpha. Ainsi un alpha de 50% correspondra à la valeur 255/2 soit 127.

enfin dans le cas où je ne veuille pas mettre de fond ou de contour à mon dessin il nous suffira d’écrire l’une des fonctions suivante : noFill() ou noStroke();

Nous avons donc vu toute les bases de processing pour effectuer notre premier skecth. Profitons en alors pour faire notre premier dessin en utilisant ce que l’on connait.

Nous allons dessiner une sketch de 710*500, contenant un grille de point espacer de 10 pixels chacun et avec un cercle de 100 pixel de diametre sans contour et de remplissage blanc à 50% d’alpha.

Cela nous donne donc

int space = 10;

void setup()
{
size(710, 500, JAVA2D);
smooth();
}

void draw()
{
background(0);
for(int i = 0; i<width; i+=space)
{
for(int j = 0; j<height; j+=space)
{
stroke(255);
point(i, j);
}
}

noStroke();
fill(255, 127);
ellipse(width/2, height/2, 100, 100);
}

ce qui nous donnera :

ixd_processingIntro_02

Secured By miniOrange