Entre explosion et fantômes


INTRODUCTION

Ce croquis suggère une curieuse matière un peu visqueuse qui apparaît sous la forme d’une explosion, s’écoule le long de l’écran et se stabilise en bas dans une sorte de flaque qui prend progressivement l’allure d’une barre, comme une épée de Jedaï dans la Guerre des Etoiles. Eventuellement, si l’écoulement n’arrive pas à son terme, la masse en forme de fuseau se resserre et s’efface progressivement, puis disparaît comme un fantôme.

En modifiant quelques paramètres on obtient toutes sortes de variations. Cet effet est pressenti pour, par exemple, un Brodock à plumeau ( un Brodock à plumeau est une sorte de bouée hérissée de poils explosifs, quelque chose comme un mollusque chimérique à réaliser dans le cadre d’une galerie de logiciels monstrueux ).

TELECHARGER

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

 

EXPLICATIONS

1. Principe

A la base il y a simplement une concentration de vecteurs, tous de sens différents, mais qui, à partir d’un centre commun donnent lieu à des projections de particules et un rayonnement. Les particules projetées sont ensuite soumises à pesanteur. Elles s’alourdissent petit à petit et l’ensemble finit par s’effondrer vers le bas. Les particules rebondissent en bas, remontent et retombent jusqu’à stabilisation.

2. Mise en forme et initialisation

Les vecteurs sont réalisés sous la forme d’une structure qui comprend quatre floats et un entier. Les quatre floats sont pour la précision de calculs en nombre à virgule. Ils concernent les deux points de coordonnées du vecteur. Le premier comme origine ( x, y ) et le second comme distance qui lui est ajoutée. L’entier « couleur » stocke une couleur. Le type « VECTEUR » est défini comme suit au début du programme :

aaaaatypedef struct {
aaaaaaaaafloat x, y, dx, dy;
aaaaaaaaaint couleur;
aaaaa} VECTEUR;

Ensuite il y a besoin d’un nombre de vecteurs. Le plus simple est d’avoir un tableau de structures VECTEUR. Il faudra pouvoir tester le programme avec des nombres différents de vecteurs, d’où la définition d’une macro, ce qui donne :

aaaaa#define NB_VECTEURS 2000
aaaaaVECTEUR VECT[NB_VECTEURS];

Notons également le besoin de nombres aléatoires à virgule. Pour ce faire la macro « frandom » est définie de la façon suivante :

aaaaa#define frandom( n ) ( ( (float)rand( ) / ( float )RAND_MAX ) * ( n ) )

La valeur de retour de la fonction « rand » est convertie en float par un « cast » ensuite elle est divisée par la valeur maximum que peut retourner « rand », elle-même convertie en float ( la macro RAND_MAX est définie dans la librairie utilitaire <stdlib.h> ). On a donc un nombre compris entre 0 et 1. Pour finir il est multiplié par « n » afin d‘avoir un nombre entre 0 et « n » avec la précision d’un flottant.
Comme pour les autres programmes il y a les deux macros pour la taille de l’écran, le pointeur « BITMAP *page » et une palette « pal ».

Initialisation des vecteurs

L’initialisation des vecteurs se fait en deux étapes. La première consiste à donner des valeurs de position et de distance. La seconde est un petit calcul qui permet de se situer sur un disque de rayon aléatoire :

(1)aaaaavoid init_explosion(int x, int y)
aaaaa{
aaaaaint i;
aaaaafloat lon, lx, ly, dist;

(2)aaaaaaafor( i = 0; i < NB_VECTEURS; i++ ) {
aaaaaaaaaaaaaVECT[ i ].x = x;
aaaaaaaaaaaaaVECT[ i ].y = y;
aaaaaaaaaaaaaVECT[ i ].dx = frandom( 2 ) - 1;
aaaaaaaaaaaaaVECT[ i ].dy = frandom( 10 ) - 5;

(3)aaaaaaaaaaalx = VECT[ i ].dx;
aaaaaaaaaaa aaly = VECT[ i ].dy;
aaaaaaaaa aaaalon = sqrt( lx*lx + ly*ly );
aaaaaaaaa aaaaif( lon != 0.0 )
aaaaaaaaaa aaaaaaalon = 1.0 / lon;

aaaaaaa aaaaaadist = frandom( 2.5 );
aaaaaaaaaaaa aVECT[ i ].dx *= lon*dist;
aaaaaaaaaaaaa VECT[ i ].dy *= lon*dist;
(4)aaaaaaaaaaaVECT[ i ].couleur = 255;
aaaaaaaaa}
aaaaa}

(1) La fonction « init_explosion » ne renvoie rien mais a deux paramètres, deux entiers. En fait c’est une position qui lui est passée. La fonction utilise quelques variables locales, un entier et quatre float.

(2) Boucle qui passe chaque vecteur en revue. Le premier point est d’attribuer des valeurs de position et de distance à la position pour chacun des vecteurs. Les vecteurs prennent tous la même position ( x, y ) passée en argument à la fonction. La distance est aléatoire dans la fourchette de – 1 à 1 pour dx et de – 5 à 5 pour dy.

(3) A cette étape de la fonction, par rapport au centre en (x, y), l’ensemble des vecteurs est enfermé dans un rectangle de deux unités horizontales sur 10 verticales. Ce n’est pas inintéressant mais l’objectif est ici de passer en coordonnées polaires et de se situer à l’intérieur d’un disque.
Les variables « lx » et « ly » sont utilitaires juste pour simplifier l’écriture. Tout d’abord, c’est le calcul de la longueur à partir du théorème de Pythagore. La fonction « sqrt » renvoie la racine carré de (dx2 + dy2), résultat stocké dans « lon ». Si ce résultat n’est pas nul, il est inversé et avec multiplication par dx on obtient le cosinus de l’angle et le sinus en multipliant par dy. La variable « dist » donne un rayon dans la fourchette de 0 à 2,5 et on passe en coordonnées polaires. Comme tous les vecteurs ont même origine ( x, y ) ils sont tous enfermés cette fois dans le disque de centre ( x, y ) et de rayon 2,5.

(4) En ce qui concerne la couleur nous avons opté pour que tous les vecteurs prennent la même couleur initiale, la position 255 de la palette. Le souhait est d’avoir au centre une couleur très claire et qui décroisse avec l’éloignement.

Initialisation de la palette


En ce qui concerne la palette il n’y a pas tellement de commentaires techniques à faire. C’est un dgradé vers le blanc avec des valeurs aléatoires de rouge. En commençant par le rouge, puis le vert et le bleu en remontant vers le blanc on obtient des verts-jaunes et de reflets de bleus très clairs à proximité du blanc.

(1)aaaaavoid palette(void)
aaaaa{
aaaaaint i;
(2)aaaaaaapal[ 0 ].r=0;
aaaaaaa aapal[ 0 ].g=0;
aaaaaa aaapal[ 0 ].b=0;
(3)aaaaaaafor ( i=1; i < 64; i++ ) {
aaaaaaaa aaaapal[ i ].r=rand( )%64;
aaaaaaaaaa aapal[ i ].g=0;
aaaaaaaaaa aapal[ i ].b=0;
}
(4)aaaaaaafor ( i=64; i<128; i++ ) {
aaaaaaaaaa aapal[ i ].r=rand( )%64;
aaaaaaaaaa aapal[ i ].g=i - 64;
aaaaaaaaaa aapal[ i ].b=0;
aaaaaaaaa}
(5)aa aaaafor ( i=128; i < 256; i++ ) {
aaaaaaaaaa aa pal[ i ].r=rand( )%64;
aaaaaaaaaa aa pal[ i ].g=63;
aaaaaaaaaa aa pal[ i ].b=( ( i – 128 ) >> 1);
aaaaaaaaa}
(6)aaaaaaaset_pallete( pal );
aaaaa}

(1) La fonction « palette » ne prend pas d’argument et ne renvoie rien.

(2) la position 0 est initialisée avec la couleur noire.

(3) Des positions 1 à 63 le rouge prend une valeur au hasard entre 0 et 63. Le vert et le bleu restent à 0.

(4) Des positions 64 à 127 toujours pareille pour le rouge, le vert augmente de 1 en un et le bleu reste à 0.

(5) Des positions 128 à 256 le rouge est toujours aléatoire, le vert est fixé à 63 et le bleu augmente de 1 en 1 par pas de deux, l’expression i >> 1 revient à diviser i par deux et dans la fonction il faudra qu’il augmente de 2 pour avancer de 1.


3 Processus générateur

Boucle du processus

Commençons par la boucle du processus placée dans la fonction main du programme .

aaaaaaa(…)
(1)
aaaaapalette ( );
aaaaaa apage = create_bitmap ( ECRAN_X, ECRAN_Y );
aaaaa aaclear ( page );
aaa aaaainit_explosion ( ECRAN_X/2, ECRAN_Y/2 );

(2)aaaaado {
aaaaa aaaaaif ( key[ KEY_ENTER ] )
(3)
aaaaaaaaaaainit_explosion ( rand( )%ECRAN_X, rand( )%ECRAN_Y );

(4)aaaaaaaabouge_vecteurs ( page );
aaaaaaaa aaefface_progressif ( page );
aaaa aaaaaablit(page, screen, 0, 0, 0, 0, ECRAN_X, ECRAN_Y);

(5)
aaaaa} while(!key[KEY_ESC]);
aaaaaa a(…)

(1) Tout d’abord il y a les initialisations des globales, à savoir la palette et la page intermédiaire du montage de l’image. Ensuite un appel à la fonction « init_explosion » avec en argument la position du centre de l’écran afin de démarrer le programme sur une explosion au centre de l’écran.

(2) Le type de boucle choisi est do-while de façon à ce qu’il y ait au moins une explosion même dans le cas ou la touche Echap aurait déjà été pressée, c’est en effet le test d’arrêt de la boucle ligne (5).

(3) A chaque pression de la touche Entrée il y a une nouvelle initialisation avec une position aléatoire dans l’écran.

(4) Après initialisation, vient le mouvement, avec l’itération d’une part de la fonction « bouge_vecteurs » qui anime les vecteurs, et d’autre part, l’effet de disparition progressive des couleurs avec la fonction « efface_progressif ». Les modifications de l’image sont faites en mémoire avec la bitmap page et pour finir l’image obtenue est plaquée à l’écran avec la fonction « blit ».

Animation des vecteurs

Il s’agit des instructions suivantes de la fonction « bouge_vecteurs » :

(1)aaaaafloat poids = 0.1;

(2)aaaaavoid bouge_vecteurs(BITMAP *bmp)
aaaaaaa{
aaaaaaaint i;
aaaaaaaVECTEUR *v = VECT;

(3)
aaaaaaaaafor ( i = 0; i < NB_VECTEURS; i++, v++ ) {
(4)
aaaaaaaaaaaaav->y += v->dy;
aaaaaaaaaaaaaa aif ( v->y > ECRAN_Y-2 ) {
aaaaaaaaaaaa aaaaaaav->y = ECRAN_Y-2;
aaaaaaaaaaaaaaaaa aav->dy = -( v->dy ) / 1.5;
aaaaaaaaaaaaaaaa}
aaaaaaaaaaaaaaaaelse if ( v->y < 2 ) {
aaaaaaaaaaaaaaaa aaav->y = 2;
aaaaaaaaaaaaaaaa aaav->dy = -( v->dy ) / 1.5;
aaaaaaaaaaaaaaaa}
aaaaaaaaaaaaaaaaelse
aaaaaaaaaaaaaaaaaaaav->dy += poids;

(5)aaaaaaaaaa aaav->x += v->dx;
aaaaaaaaaaaaaaaaif ( v->x > ECRAN_X-2 ) {
aaaaaaaaaaaaaaaaaaaav->x = ECRAN_X-2;
aaaaaaaaaaaaaaaaaaaav->dx = -( v->dx ) / 1.5;
aaaaaaaaaaaaaaaa}
aaaaaaaaaaaaaaaaelse if( v->x < 2 ) {
aaaaaaaaaaaaaaaaaaaav->x = 2;
aaaaaaaaaaaaaaaaaaaav->dx = -( v->dx ) / 1.5;
aaaaaaaaaaaaaaaa}
(6)aaaaaaaaaaaaabmp->line[ ( int ) v->y ][ ( int ) v->x ] = v->couleur;
aaaaaaaaaa}
aaaaa}

(1) La fonction utilise une variable « poids » ici déclarée globale afin éventuellement de pouvoir modifier sa valeur à partir d’une autre fonction dans le programme. « poids » va servir à accentuer le déplacement vers le bas de l’écran en y.

(2) « bouge_vecteurs » prend une bitmap de référence en argument et ne renvoie pas de valeur. La déclaration locale d’un pointeur de type VECTEUR* est motivée afin de soulager l’écriture. Il est initialisé avec la première adresse du tableau VECT, la première position.

(3) Tous les vecteurs sont passés en revue, à noter l’incrémentation parallèle du pointeur.

(4)(5) Pour chacun d’entre eux il y a d’abord les modifications de la position verticale et ensuite les modifications de la position horizontale en (5). Dans les deux cas la distance est ajoutée à la position courante. Vient ensuite un test sur les bords de l’écran.
Le pourtour de l’écran fait l’objet d’une marge de deux unités réservées au fonctionnement de la fonction suivante « efface_progressif ».
Tous dépassements, gauche-droite ou haut-bas, se traduisent par le ralentissement du déplacement. La valeur donnée est arbitraire, obtenue par tâtonnement, elle pourrait faire l’objet de modification à la volée.
Du point de vue vertical le déplacement vers le bas en dy est augmenté du « poids », ce qui accélère sa chute et crée un effet de pesanteur.

(6) Pour finir, le vecteur qui est visualisé par le déplacement d’un point est affiché dans la bitmap de référence selon le principe que nous avons détaillé au chapitre Onze.
Disparition progressive des couleurs
Pour chaque position de l’image, il s’agit de faire la moyenne des couleurs des positions est, nord, ouest et sud et de diminuer le résultat progressivement jusqu’à 0 :

(1)aaaaavoid efface_progressif(BITMAP *bmp)
aaaaa a{
aaaaa aaint x, y, t;

aaaaaaaaaaafor ( y = 1; y < ECRAN_Y-1; y++ )
aaaaaaaaaaaaaaafor ( x = 1; x < ECRAN_X-1; x++ ){

(2)aaaaaaaaaaaaaaaat = ( bmp->line[ y-1 ][ x ] + bmp->line[ y+1 ][ x ] +
aaaaaaaaaaaaaaaaaaaaaaabmp->line[ y ][ x-1 ] + bmp->line[ y ][ x+1 ] ) / 4;
aaaaaaaaaaaaaaaaaaat = ( t < 2 ) ? 0 : t - 2 ;
(3)
aaaaaaaaaaaaaaaabmp->line[ y ][ x ] = ( unsigned char ) t ;
aaaaaaaaaaaaaaa}
aaaaaa}

(1) « efface_progressif » ne renvoie pas de valeur et a comme paramètre unique une bitmap de référence.

(2) Pour chaque position de la bitmap passée en argument, une variable « t » stocke tout d’abord l’addition des quatre voisines et la divise par quatre. Ensuite l’expression conditionnelle décrémente t de 2 si t est supérieur à 2 et lui attribue 0 sinon.

(3) Pour finir, t qui est de type int est casté en unsigned char et affectée à la position courante ( x, y).