Animation de particules sur un réseau hexagonal

SimplX lance en 2017 une activité de créativité numérique. Fruit de cette nouvelle dynamique, cet article présente les étapes de la création d’un ensemble de particules en mouvement sur une grille hexagonale.

De la mise en place d’un système de coordonnées jusqu’aux élucubrations visuelles finales, en passant par le paramétrage de la physique des particules et de l’aléatoire, les étapes sont illustrées par des animations et des propositions de code JavaScript.

1 – Mettre en place le système de coordonnées

La construction de la grille hexagonale nécessite la définition d’un mapping avec les coordonnées cartésiennes discrètes usuelles. Comme illustré ci-dessus, la relation entre ces deux systèmes de coordonnées passe par une rotation, une légère translation (optionnelle) puis un calcul dépendant de la parité. La fonction résultante qui prend en compte tous les cas possibles peut s’écrire ainsi :

Hexocet.hexCoordsToXY = function(Hx, Hy) {
  // Hx and Hy are integers corresponding to vertices coordinates in Hex space
  [...]
  var isSumEven = ((Hx + Hy) % 2 == 0 ? 1 : 0);
  x = Hx;
  y = 1 / Math.sqrt(3) * (3 * Hy + 1 + isSumEven);
  [...]
  return { x: hexSize * x, y: hexSize * y };
};

La mise en place de cette fonction et de sa fonction réciproque a constitué la majeure partie du travail. Ce qui se résume finalement en trois lignes de code – faisant intervenir la somme Hx+Hy – est la factorisation d’un grand nombre de cas possibles.

2 – Faire naître et contrôler les particules

Les particules sont des objets JavaScript contenus dans un tableau mis à jour à chaque itération de l’animation. Les propriétés principales d’une particule sont la position, la vitesse et la cible. La cible est un couple de coordonnées targetHx et targetHy : c’est le point que la particule va suivre dans son mouvement. À cela s’ajouteront des propriétés graphiques (hue, composante de couleur dans le modèle HSL) et système comme l’âge de la particule, qui pourra déterminer la façon dont sera elle sera dessinée.

Les particules évoluent sur l’aire de dessin, ici sur un <canvas> HTML5. Le cycle de mise à jour est constitué de la naissance d’un certain nombre de particules, de l’appel à une fonction move() qui modifiera les propriétés de la particule puis de l’appel à fonction de rendu draw().

Hexocet.update = function() {
  this.stepCount++;
  
  // Birthrate management
  if (this.stepCount % this.birthPeriod == 0 && this.stepCount < 60000) {
    this.birth();
  }
  
  this.move();
  this.draw();
};

Comme mentionné plus haut, les particules vont être attirées vers leurs cibles. Ces cibles changent avec une fréquence aléatoire (i.e. probabilité fixe à chaque itération), et sautent de sommet en sommet sur la grille hexagonale. L’attraction de la particule à sa cible est de type ressort – donc d’une force proportionnelle à la distance qui les sépare – avec une atténuation visqueuse, qui dépend de la vitesse. Ainsi la particule rejoint sa cible avec plus ou moins de férocité et de liberté. Les composantes X et Y des forces en jeu sont utilisées plutôt que leurs natures radiales.

[...]
// Acceleration based on target
var targetXY = this.hexCoordsToXY(seed.targetHx, seed.targetHy);

// Spring
var K = this.springStiffness;
var accX = - K * (seed.x - targetXY.x);
var accY = - K * (seed.y - targetXY.y);

// Viscosity
var visc = this.viscosity;
accX -= visc * seed.xSpeed;
accY -= visc * seed.ySpeed;

// Speed
seed.xSpeed += accX;
seed.ySpeed += accY;

// Position, with added canvas base size in order to maintain patterns accross zoom levels
seed.x += 0.01 * seed.xSpeed * this.canvasBase;
seed.y += 0.01 * seed.ySpeed * this.canvasBase;

Il s’agit finalement d’ajuster la raideur K du ressort et la viscosité du système pour obtenir différents comportements. Ces paramètres sont définis en début de code dans le scope JavaScript global.

Les fondations étant mises en place, voici un premier aperçu de ce que l’on peut obtenir.

[codepen_embed height=500 theme_id=1 slug_hash=’xgbpWp’ user=’simplx’ default_tab=’result’ animations=’run’]
See the Pen Lorem Ipsum by Alexandre Andrieux – SimplX
[/codepen_embed]

 

3 – Ajuster les lois qui régissent le système

Les variables globales, qui permettent de modifier rapidement et d’un seul coup le comportement des particules et certaines de leurs propriétés graphiques, incluent :

  • La fréquence de naissance spontanée d’une nouvelle particule
  • La probabilité de rebond de la cible d’une particule à chaque itération (généralement entre 0.5% et 5%)
  • La raideur du ressort qui lie les particules à leur cible
  • La viscosité du milieu où évoluent les particules
  • Une valeur maximale de la population en particules

À cette liste s’ajoute l’opacité maximale de dessin d’une particule. En effet, on peut choisir de laisser les particules dessiner une trace indélébile derrière elles ou alors de l’effacer progressivement, créant ainsi un effet visuel de type trainée.

Deux techniques différentes ont été éprouvées pour obtenir cet effet. La première (classique) consiste à appliquer une légère couche de noir – ou blanc sur fond blanc – d’opacité avoisinant les 5% sur l’ensemble du canvas à chaque itération, pour effacer progressivement les anciennes positions des particules. La seconde consiste à mémoriser les N dernières positions de toutes les particules pour les dessiner avec une opacité qui décroit avec l’ancienneté (c.f. un article de Martijn Brekelmans sur cette alternative). Cette méthode-là nécessite de manipuler N fois plus de variables par itérations, ce qui limite la population maximale.

Le code suivant est utilisé pour la première option :

Hexocet.draw = function() {
  // Add translucid layer for trace effect
  var opa = 0.05;
  this.ctx.rect(0, 0, this.canvas.width, this.canvas.height);
  this.ctx.fillStyle = "rgba(0, 0, 0, " + opa + ")";
  this.ctx.fill();

  [...]
};

Voici un exemple de résultat :

[codepen_embed height=500 theme_id=1 slug_hash=’ygyKXw’ user=’simplx’ default_tab=’result’ animations=’run’]
See the Pen Lorem Ipsum by Alexandre Andrieux – SimplX
[/codepen_embed]

Il est également possible d’animer la variable de probabilité de rebond de la cible au cours du temps. Avec une fonction sinusoïdale simple qui oscille entre 0 et 10% de chance de rebond, on obtient un système rythmé par des pulsations :

[codepen_embed height=500 theme_id=1 slug_hash=’pRvLwG’ user=’simplx’ default_tab=’result’ animations=’run’]
See the Pen Lorem Ipsum by Alexandre Andrieux – SimplX
[/codepen_embed]

La seconde technique de trainée peut donner un rendu plus réactif et plus net, au détriment de la performance.

[codepen_embed height=500 theme_id=1 slug_hash=’ZLWjBJ’ user=’simplx’ default_tab=’result’ animations=’run’]
See the Pen Lorem Ipsum by Alexandre Andrieux – SimplX
[/codepen_embed]

 

4 – Faire naître des particules au survol de la souris

Dans un souci d’ajouter une touche d’interactivité à la création, il est possible d’implémenter sans trop d’efforts une génération de particules qui dépend de la position du curseur de la souris. Voici un aperçu de la fonction de naissance d’une particule :

Hexocet.birth = function(xBirth, yBirth, seed) {

  // Give birth around the canvas center or a given grid point
  var targetH = this.XYtoHexCoords(xBirth || this.xC, yBirth || this.yC);

  // I said AROUND
  var spreadArea = 3;
  targetH.Hx += Math.floor(spreadArea * (-0.5 + Math.random()));
  targetH.Hy += Math.floor(spreadArea * (-0.5 + Math.random()));

  // Convert in Cartesian coords
  var targetXY = this.hexCoordsToXY(targetH.Hx, targetH.Hy);
  var x = targetXY.x;
  var y = targetXY.y;
  
  var seed = seed || {
    xLast: x,
    x: x,
    xSpeed: 0,
    yLast: y,
    y: y,
    ySpeed: 0,
    targetHx: targetH.Hx,
    targetHy: targetH.Hy,
    age: 0,
    hue: 205 + 15 * Math.sin(this.stepCount / 50),
    posHistory: [{ x: x, y: y }, { x: x, y: y }]
  };

  this.seeds.push(seed);
};

Cette fonction prend donc en paramètres les coordonnées du point où naissent les particules. On remarque également la possibilité de faire naitre la particule dans un certain rayon autour de ce point, pour une plus grande dispersion. À partir de là, il suffit d’appeler cette fonction avec les coordonnées de l’évènement du pointeur, au survol ou au clic selon les appétences.

jQuery('canvas#hexocet').on('mousemove', function(event) {
  var x = event.pageX,
      y = event.pageY;
  Hexocet.birth(x, y);
});

Résultat :

[codepen_embed height=500 theme_id=1 slug_hash=’zNxjxK’ user=’simplx’ default_tab=’result’ animations=’run’]
See the Pen Lorem Ipsum by Alexandre Andrieux – SimplX
[/codepen_embed]

 

5 – Travailler les propriétés graphiques

À ce stade, il est possible de jouer avec toutes les propriétés dont disposent les particules. En particulier, exploiter l’âge d’une particule – que l’on peut réinitialiser à 0 à chaque changement de cible – peut s’avérer intéressant.

Voici tout d’abord un aperçu de la fonction draw() utilisée jusqu’à présent :

Hexocet.draw = function() {
  // Add translucid layer for trace effect
  [...]

  for (var key in this.seeds) {
    var seed = this.seeds[key];

    // HSLA
    var hsla = {
      h: seed.hue,
      s: '70%',
      l: '50%',
      a: this.particleOpacity
    };

    // Line width
    var wLine = 2;

    // Stroke
    this.ctx.strokeStyle = 'hsla(' + hsla.h + ', ' + hsla.s + ', ' + hsla.l + ", " + hsla.a + ")";
    this.ctx.lineWidth = wLine;
    this.ctx.lineCap = "round";
    this.ctx.beginPath();
    this.ctx.moveTo(seed.xLast, seed.yLast);
    this.ctx.lineTo(seed.x, seed.y);
    this.ctx.stroke();

    [...]
  }
};

Cette utilisation classique des méthodes du canvas permet de dessiner un chemin depuis la dernière position de la particule vers la plus récente.
Il est aussi possible de dessiner un rond sur la dernière position occupée. En tirant profit de la propriété age de la particule, on peut faire grandir les particules tant que leur cible reste inchangée :

// Loop history to draw positions
for (var p = 0; p < seed.posHistory.length; p++) {
  var a = hsla.a * p / seed.posHistory.length;
  var rad = 2 + seed.age / 5;
  // Stroke
  this.ctx.beginPath();
  this.ctx.strokeStyle = 'hsla(' + hsla.h + ', ' + hsla.s + ', ' + hsla.l + ", " + a + ")";
  this.ctx.arc(seed.posHistory[p].x, seed.posHistory[p].y, rad, 0, 2 * Math.PI, true);
  this.ctx.stroke();
}

À noter ici le code permettant d’obtenir une effet de trainée de type historique. Résultat :

[codepen_embed height=500 theme_id=1 slug_hash=’PWNdLQ’ user=’simplx’ default_tab=’result’ animations=’run’]
See the Pen Lorem Ipsum by Alexandre Andrieux – SimplX
[/codepen_embed]

En inversant la loi de croissance du rayon :

[codepen_embed height=500 theme_id=1 slug_hash=’GrZYKy’ user=’simplx’ default_tab=’result’ animations=’run’]
See the Pen Lorem Ipsum by Alexandre Andrieux – SimplX
[/codepen_embed]

 

6 – Conclusion

L’exploitation du système de coordonnées hexagonales s’est ici limitée à la mise en place et l’utilisation de certaines propriétés de base :

  • La fréquence de naissance
  • La probabilité de rebond
  • La mécanique liant la particule à sa cible (ressort et viscosité)
  • La longueur de trainée
  • La couleur de la particule en fonction du temps
  • La relation entre âge et taille de la particule

Les possibilités sont multiples, tant du côté graphique sur la manière de dessiner une particule, que du côté physique en jouant sur les forces en jeu. La création n’a de limite que notre imagination !

[codepen_embed height=500 theme_id=1 slug_hash=’VPyMoy’ user=’simplx’ default_tab=’result’ animations=’run’]
See the Pen Lorem Ipsum by Alexandre Andrieux – SimplX
[/codepen_embed]

Le code de cette création – Hexocet – a été entièrement réalisé sur CodePen, une plateforme pour l’expérimentation HTML/CSS/JavaScript.

Retrouvez les autres créations SimplX sur http://codepen.io/simplx/

Related Post

Authentification d’API...

Bienvenue en 2016, nous évoluons aujourd’hui dans un monde où tout n’est...

Documenter son code Angular...

Vous êtes fatigué(e) de lire et relire les lignes de code du dernier projet en...

Génération de variables...

Lorsque l’on développe une application Web ou mobile, il n’est pas rare...

Laisser un commentaire