Troupeaux de pirlufs et sociale mécanique


INTRODUCTION

Ce programme est un germe de création. Il est réalisé sur la base d’un programme original de Robin Fercoq : "Volpurna". Son principe fondamental est extrait et restitué sous une forme minimaliste, ce qui en fait une source d’inspiration et de réflexion possible, en accord avec son auteur qui met son propre code source à disposition du public.

Nous proposons l'interprétation suivante : "Troupeaux de Pirlufs et sociale mécanique" fait état d’une population en mouvement soumise à "l’onde mega" du docteur Septimus. Le docteur Septimus est un personnage d’une aventure de Blake et Mortimer "La marque jaune", bande dessinée de Edgar Jacobs (édition Blake et Mortimer, Bruxelles 1987 - première édition 1953).

Dans cette création ce n’est pas le produit graphique qui intéresse d'abord, même si la visualisation des résultats est importante. Ce sont surtout les mouvements d’une population de carrés pris dans des réseaux de forces, comme dans des courants marins. Les mouvements d'ensemble évoquent parfois des bancs de poissons et d'oiseaux mais ces figures, comme celles d'un automate cellulaire, ne sont pas programmées en tant que tel.

TELECHARGER

aaaTexte des explications, aaaExecutable (Windows), aaaSource (utilise la librairie Allegro)

 

EXPLICATIONS

1. Principe

La présentation faite par l’auteur est la suivante :

« Volpurna simule l'agitation de particules en interaction. Chaque particule peut choisir soit de suivre son propre déplacement rectiligne soit d'adopter le mouvement de la particule la plus proche au-delà d'une certaine distance. La probabilité de suivre plutôt son « instinct » ou plutôt le mouvement des autres est réglée par l'utilisateur, de même que la distance minimum d'interaction. En manipulant ces paramètres globaux et leur variabilité inter-individuelle il est possible d'explorer continuellement différents régimes collectifs, moléculaires, chaotiques, individualistes ou totalitaires »

L’auteur s’interroge sur l’articulation entre mouvements individuels et mouvements collectifs. De ce fait la nature exacte du logiciel est hybride. C’est un travail qui n’est réservé ni au visuel, ni à la vie artificielle, ni à la physique ou aux mathématiques, il prend des accents éventuellement politiques, philosophiques et critiques… Le modèle du fonctionnement est le suivant :
Il y a une population d’objets capables de se déplacer et sensibles à l’attraction des objets alentours en fonction de la distance ou de la proximité. La sensibilité à l’attraction collective est dosée avec le mouvement horizontal de la souris. Totalement à gauche le collectif supprime toute possibilité de mouvement individuel. Totalement à droite les objets n’ont aucune interaction. Le mouvement vertical de la souris contrôle la distance des interactions possibles. Dans les deux cas toutes les graduations intermédiaires sont permises.

2. Mise en forme et initialisation

Structure de données

un objet est défini par une structure dont les champs correspondent aux paramètres de ses propriétés (position, déplacements, couleur etc.). Le nombre des objets est défini par une macro et tous les objets sont rangés dans un tableau sous la forme d’une structure :

(1) aaaaaa#define NB_OBJ 300

(2) aaaaaastruct{

(3) aaaaaaint x, y;
(4) aaaaaaint mdx,mdy;
(5) aaaaaaint adx,ady;
(6) aaaaaaint resdx,resdy;
(7) aaaaaaint mcol,acol,rescol;

(8) aaaaaa}obj[NB_OBJ];

(1) La macro NB_OBJ donne un nombre d’objets qui sont des structures.

(2) (8) La structure pour les objets est définie au moment de la déclaration du tableau « obj » de la taille de NB_OBJ. Elle n’a pas de nom et ne sera à aucun moment nécessaire en dehors du tableau.

(3) Les variables « x » et « y » déterminent la position de l’objet à l’écran.

(4) « mdx » et « mdy » sont les variables pour le vecteur dit « maître » à savoir la direction par défaut que prend l’entité lorsqu’elle n’est soumise à aucune influence.

(5) « adx » et « ady » correspondent à la direction courante d’une entité soumise ou non à influence.

(6) « resdx » et « resdy » sont des variables qui stockent des valeurs intermédiaires de directions pendant le calcul des influences mutuelles entre entités.

(7) Schéma identique à celui du déplacement pour la couleur avec « mcol » pour la couleur maîtresse, « acol » pour la couleur courante et « rescol » pour les calculs intermédiaires.

En dehors du tableau de structures quelques macros habituelles, pour la taille de l’écran et l’obtention d’un nombre pseudo-aléatoire avec un modulo.

aaaaaa#define ECRAN_X 800
aaaaaa#define ECRAN_Y 600
aaaaaa#define X2 ( ECRAN_X/2 )
aaaaaa#define Y2 ( ECRAN_Y/2 )
aaaaaa#define irand( n ) ( rand( )%( n ) )

La fonction d’initialisation

A l’aide d’une boucle « for » chaque champ reçoit une valeur aléatoire. Cette valeur est puisée dans la fourchette de l’écran pour la position, dans une fourchette entre -5 et 5 pour les vecteurs, et dans la fourchette de 1 à 255 pour la couleur

aaaaaavoid initialisation(void)
aaaaaa{
aaaaaaint n;
aaaaaaaaaaaafor(n=0;n<NB_OBJ;n++){
aaaaaaaaaaaaaaaaaaobj[n].x=irand(ECRAN_X);
aaaaaaaaaaaaaaaaaaobj[n].y=irand(ECRAN_Y);
aaaaaaaaaaaaaaaaaaobj[n].mdx=obj[n].adx=irand(11)-5;
aaaaaaaaaaaaaaaaaaobj[n].mdy=obj[n].ady=irand(11)-5;
aaaaaaaaaaaaaaaaaaobj[n].mcol=obj[n].acol=irand(255)+1;
aaaaaaaaaaaa}
aaaaaa}

3. Processus générateur


Après création d’une palette et création d’une bitmap pour un affichage en double buffering, la fonction d’initialisation est appelée. Ensuite vient la boucle motrice avec pour test d’arrêt la pression sur la touche Echap. L’action se décompose en deux fonctions, sans appel à des sous fonctions hormis les fonctions d’affichage de l’environnement.

aaaaaa(…)
aaaaaafaire_palette(gPal);
aaaaaabmp=create_bitmap(ECRAN_X,ECRAN_Y);

aaaaaainitialisation();
aaaaaawhile(!key[KEY_ESC]){
aaaaaaaaaaaacalcul();
aaaaaaaaaaaaaffiche (bmp);
aaaaaa}


Détail de la fonction « calcul »

Le premier point de l’action consiste à trouver pour chaque entité celle qui est la plus proche, compte-tenu de la position verticale de la souris. Pour calculer la distance entre deux points dans l’écran le théorème de Pythagore est utilisé. Avec la distance horizontale et la distance verticale entre chaque entité, on finit par trouver l’hypoténuse la plus courte. Le second point, selon cette fois la position horizontale de la souris, est, soit de prendre comme résultat les valeurs du vecteur maître de l’entité en question, soit les valeurs du vecteur courant de celle qui lui est la plus proche :

(1) aaaaaavoid calcul(void)
aaaaaaaaa{
aaaaaaaaaint dx, dy, n, i;
aaaaaaaaaint resobj, dist, mindist;

(2) aaaaaaaaaaaafor ( n=0 ; n < NB_OBJ; n++ ){

aaaaaaaaaaaaaaaaaaaaaif(irand(ECRAN_X)<mouse_x){
aaaaaaaaaaaaaaaaaaaaaaaaaaaobj[n].resdx=obj[n].mdx;
aaaaaaaaaaaaaaaaaaaaaaaaaaaobj[n].resdy=obj[n].mdy;
aaaaaaaaaaaaaaaaaaaaaaaaaaaobj[n].rescol=obj[n].mcol;
aaaaaaaaaaaaaaaaaaaaaaaaaaacontinue;
aaaaaaaaaaaaaaaaaaaaa}
(3) aaaaa aaaaaaaaaaaamindist=ECRAN_X*ECRAN_X;
aaaaaaaaaaaaaaaaaaaaaresobj=-1;

(4) aaaaaaaaaaaaaaaaaafor ( i = 0 ; i < NB_OBJ ; i++ ){
aaaaaaaaaaaaaaaaaaaaaaaaaaaif ( i == n )
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacontinue;
(5) aaaaaaaaaaaaaaaaaaaaaaaadx = abs ( obj[ n ].x - obj[ i ].x );
aaaaaaaaaaaaaaaaaaaaaaaaaaaif ( dx > X2 )
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaadx = ECRAN_X - dx;

aaaaaaaaaaaaaaaaaaaaaaaaaaady = abs ( obj[ n ].y - obj[ i ].y );
aaaaaaaaaaaaaaaaaaaaaaaaaaaif( dy > Y2 )
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaady = ECRAN_Y - dy;

(6) aaaaaaaaaaaaaaaaaaaaaaaadist = dx*dx+dy*dy;
aaaaaaaaaaaaaaaaaaaaaaaaaaaif( dist <= mindist && dist > mouse_y*mouse_y ){ resobj = i;
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaamindist = dist;
aaaaaaaaaaaaaaaaaaaaaaaaaaa}
aaaaaaaaaaaaaaaaaaaaa}
(7) aaaaaaaaaaaaaaaaaa if ( resobj >= 0 ){
aaaaaaaaaaaaaaaaaaaaaaaaaaaobj[ n ].resdx = obj[ resobj ].adx;
aaaaaaaaaaaaaaaaaaaaaaaaaaaobj[ n ].resdy = obj[ resobj ].ady;
aaaaaaaaaaaaaaaaaaaaaaaaaaaobj[ n ].rescol = obj[ resobj ].acol;
aaaaaaaaaaaaaaaaaaaaa}
aaaaaaaaaaaaaaa}
aaaaaaaaa}

(1) La fonction « calcul » ne renvoie pas de valeur et n’a pas de paramètre. Elle utilise quelques variables locales, des entiers.

(2) La première boucle for donne accès à chaque entité.
La première instruction est un test sur la position horizontale. La variable « mouse_x » fait partie de l’environnement Allegro et donne la position horizontale courante de la souris à l’écran ; la position verticale est de même donnée par la variable « mouse_y ». Si la valeur renvoyée par la fonction « rand » avec un modulo sur la taille de l’écran est inférieure à la position horizontale de la souris, les instructions du bloc sont effectuées.
Les instructions consistent à prendre comme résultat pour l’entité « n » ses propres valeurs maîtresses de déplacement et de couleur. Ainsi plus la souris sera du côté droit plus la probabilité d’exécuter les instructions sera grande, à savoir que l’entité soit dirigée par ses propres valeurs maîtresses. C’est ce qui exprime la suprématie de l’individuel sur le collectif.
L’instruction « continue » fait ensuite passer à l’entité suivante, le reste des instructions étant devenu inutile (pas d’influence).
Inversement, plus la souris est à gauche moins le bloc est accessible, et il ne l’est pas du tout avec « mouse_x » égale à 0, ce qui exprime les degrés de suprématie du collectif sur l’individuel.

(3) Dans le cas où le bloc précédent n’est pas exécuté, il s’agit de trouver l’entité la plus proche de l’entité n parmi les autres. La variable locale « mindist » est initialisée sur le carré de la distance horizontale la plus grande, la taille de l’écran. Chaque distance plus petite trouvée sera stockée à la place de la précédente de façon à ce que reste la plus petite à la fin.
Par ailleurs la variable « resobj » destinée à conserver le numéro d’indice de l’entité trouvée la plus proche est initialisée en dehors d’un indice de tableau afin éventuellement de signifier qu’aucune entité n’a été trouvée.

(4) Chaque entité regarde toutes les autres. C’est la raison d’être de la deuxième boucle for. Chaque entité « n » regarde toutes les autres « i ». D’ailleurs la première instruction consiste à passer i si i et n sont la même entité.

(5) Il s’agit de calculer la distance de l’entité n à chaque entité i. La distance horizontale est donnée par la soustraction des coordonnées horizontales de chaque entité, de même pour la distance verticale avec les coordonnées verticales. Puisqu’il s’agit de comparer les distances indépendamment des orientations, ce sont les valeurs absolues de l’objet n à l’objet i pour l’horizontale et la verticale qui sont retenues. Précisons que l’espace de l’écran est considéré comme circulaire. Les bords se rejoignent et se touchent. Sortir à gauche fait rentrer à droite et réciproquement, et de même entre haut et bas. Si donc la distance trouvée est supérieurs à la moitié de l’écran c’est que les deux entités sont plus proches en passant « par derrière ». Cette nouvelle distance est obtenue en soustrayant l’ancienne à la distance maximum qui est la taille de l’écran. Ces aspects sont identiques pour les distances horizontale et verticale.

(6) La variable « dist » stocke le carré de l’hypoténuse et elle est suivie d’un test à deux conditions. Tout d’abord si la valeur de dist est inférieure à la valeur stockée dans mindist c’est que l’on a trouvé plus près. Mais également il y a une autre contrainte qui varie avec la position verticale de la souris.
La variable mouse_y donne la position verticale courante de la souris à l’écran. Ainsi il ne suffit pas que la distance entre deux entités soit plus courte il faut également qu’elle soit supérieure au carré de la position verticale de la souris. Il s’agit de trouver l’entité la plus proche après cette distance là.
Si tel est le cas, alors les instructions sont de mémoriser la valeur de l’indice de l’entité i dans la variable resobj et de conserver cette distance trouvée comme la nouvelle plus petite distance.

(7) Une fois la distance de l’entité n à toutes les entités i comparées, le résultat, l’entité i la plus proche de n se trouve dans la variable resobj à moins qu’aucune entité n’est été trouvée, ce qui est possible si « mouse_y » est très élevé avec la souris en permanence vers le bas de l’écran. Dans ce cas resobj est toujours égale à -1. Dans le cas contraire, c’est la confrontation entre les deux entités.
L’entité n qui regarde les autres prend les valeurs courantes de l’entité i la plus proche d’elle comme résultat dans les champs prévus de la structure.


Détail de la fonction « affiche »


L’affichage est assez sobre. Les entités sont réduites à de petits carrés :

(1) aaaaaavoid affiche (BITMAP*bmp)
aaaaaaaaa{
aaaaaaaaaint n;
aaaaaaaaaaaaaaclear(bmp);
(2) aaaaaaaaaaafor ( n = 0 ; n < NB_OBJ ; n++ ){
aaaaaaaaaaaaaaaaaaaaobj[ n ].adx=obj[ n ].resdx;
aaaaaaaaaaaaaaaaaaaaobj[ n ].ady=obj[ n ].resdy;
aaaaaaaaaaaaaaaaaaaaobj[ n ].acol=obj[ n ].rescol;

(3) aaaaaaaaaaaaaaaaaobj[ n ].x = ( obj[ n ].x+obj[ n ].adx+ECRAN_X )%ECRAN_X;
aaaaaaaaaaaaaaaaaaaaobj[ n ].y = ( obj[ n ].y+obj[ n ].ady+ECRAN_Y )%ECRAN_Y;

(4) aaaaaaaaaaaaaaaaarectfill ( bmp, obj[ n ].x, obj[ n ].y, obj[ n ].x+5, obj[ n ].y+5,obj[ n ].acol );
aaaaaaaaaaaaaa}
(5) aaaaaaaaaaavline ( bmp,mouse_x, 0, ECRAN_Y-1, 255 );
aaaaaaaaaaaaaahline ( bmp, 0, mouse_y, ECRAN_X-1,255 );
aaaaaaaaaaaaaablit ( bmp, screen, 0, 0, 0, 0, ECRAN_X, ECRAN_Y );
aaaaaaaaa}

(1) La fonction « affiche » ne renvoie pas de valeur. Elle a un pointeur BITMAP* comme paramètre. C’est l’image bitmap de référence en mémoire avant copie dans la mémoire vidéo pour l’écran. Le premier appel de fonction consiste à effacer le contenu de cette bitmap.

(2) Boucle for qui passe en revue toutes les entités. Pour chacune d’entre elles le résultat du calcul est copié dans la valeur courante pour les variables de déplacement et de couleur.

(3) La nouvelle position (x, y) est calculée en respectant la circularité de l’écran.

(4) Affichage de l’entité sous la forme d’un petit carré de cinq pixels de côté de la couleur courante.

(5) Après la boucle et l’affichage des entités, affichage de la position souris à travers une ligne horizontale pour la position horizontale et une ligne verticale pour la position verticale. Enfin placage à l’écran du résultat avec appel à la fonction « blit ».