Flammes


INTRODUCTION

L'idée originale de ce programme est de Shawn Hargreaves. Elle est donnée dans l'exemples exflame.c de la librairie Allegro. Mais pour simple qu'elle ait pu paraître à son auteur elle n'est pas si triviale que ça et elle mérite d'être regardée en détail.

TELECHARGER

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

 

EXPLICATIONS

1. Principe

L’idée de feu s’appuie d’abord sur la création d’une palette de couleur : un dégradé du noir au rouge puis au blanc en passant par l’orange et le jaune. Les flammes ont une allure de dessin d’enfant, montent du bas de l’écran vers le haut, du jaune le plus clair vers le plus foncé, le rouge sombre et finalement disparaissent dans l’obscurité. Pour ce faire il y a trois principales étapes.

La première est la définition de la première ligne de feu, c’est-à-dire, puisque les flammes montent, la dernière ligne en bas de l’écran. Sur cette ligne des segments sont définis à partir de points pris au hasard comme centres des segments. La couleur des segments est un dégradé qui, de gauche à droite, part du rouge le plus sombre et rejoint à nouveau le rouge le plus sombre en passant par le blanc. Eventuellement les segments peuvent se superposer ce qui ajoute un peu de relief à la régularité du dégradé.

En second lieu il s’agit de faire monter les flammes. La ligne du bas va être recopiée sur la ligne au-dessus avec au passage un léger assombrissement de la couleur. Ce principe de recopie ligne à ligne en partant du haut et en descendant vers le bas avec assombrissement progressif de la couleur fait l’objet d‘une boucle jusqu’à ce qu’il n’y ait plus de couleur sur la ligne tout en haut de la flamme à une certaine hauteur de l’écran.

Tel quel l’algorithme donne un dégradé vers le haut des couleurs de la palette à partir des centres des segments de la ligne initiale et il n’y a pas de mouvement. Pour qu’il y ait un mouvement il faut modifier légèrement et au hasard la ligne initiale du bas, c’est l’objet de la troisième étape. Les segments sont définis par un point sur une ligne : une distance est retranchée à gauche et ajoutée à droite du point, ce qui donne le segment par rapport au point. Il suffit de modifier légèrement la position du point pour créer le décalage, base de l’animation de la flamme. La figure ci-dessous récapitule le principe de cette représentation.

aaaaa


2. Mise en forme et initialisation

Créer la palette

Le programme est en mode 8 bits de profondeur de couleur et la première fonction est celle de la création d’une palette avec un dégradé du rouge au blanc en passant par le jaune.

(1)aaaaPALETTE pal ;

(2)aaaavoid couleurs_flammes(void)
aaaaaa{
aaaaaaint i;

(3)aaaaaaafor (i=0; i<64; i++) {
aaaaaaaaaaaaapal[i].r = i;
aaaaaaaaaaaaapal[i].g = 0;
aaaaaaaaaaaaapal[i].b = 0;
aaaaaaaaa}
(4)aaaaaaafor (i=64; i<128; i++) {
aaaaaaaaaaaaapal[i].r = 63;
aaaaaaaaaaaaapal[i].g = i-64;
aaaaaaaaaaaaapal[i].b = 0;
aaaaaaaaa}
(5)aaaaaaafor (i=128; i<192; i++) {
aaaaaaaaaaaaapal[i].r = 63;
aaaaaaaaaaaaapal[i].g = 63;
aaaaaaaaaaaaapal[i].b = i-192;
aaaaaaaaa}
(6)aaaaaaafor (i=192; i<256; i++) {
aaaaaaaaaaaaapal[i].r = 63;
aaaaaaaaaaaaapal[i].g = 63;
aaaaaaaaaaaaapal[i].b = 63;
aaaaaaaaa}
(7)aaaaaaaset_palette(pal);
aaaa}


(1) La palette « pal » est déclarée en globale au début du programme.

(2) La fonction « couleurs_flammes » ne renvoie aucune valeur et n’a pas de paramètre en argument.

(3) Le rouge augmente de 0 à 63 alors que vert et bleu restent à 0 (progression du rouge de plus en plus vif).

(4) Le rouge reste à 63 et le bleu à 0, le vert augmente progressivement de 0 à 63 ( les orangers d’abord et jaunes ensuite de plus en plus vifs).

(5) Rouge et vert restent à 63, le bleu augmente progressivement de 0 à 63 ( du jaune au blanc). La formulation est intéressante, plutôt que d’écrire « i-128 » c’est « i-192 », disons par coquetterie littéraire du domaine. Pour comprendre l’expression il ne faut pas oublier que seuls les six premiers bits comptent pour le codage de la couleur et également que 192 correspond à 1100 0000 en binaire. La soustraction 128-192 en binaire donne sur un octet non signé la valeur 192, la soustraction suivante 129-192 donne la valeur 193 soit en binaire 1100 0001, ce qui fait 1 pour les six premiers bits du codage de la couleur etc.

(6) Rouge, vert et bleu restent à 63, couleur blanche des indices 192 à 255 de la palette.

(7) Activation de la palette.

Initialiser les variables

Ensuite la structure de données essentielle est un tableau déclaré en global qui sert à stocker des positions sur une ligne afin de créer les segments. Nous l’avons appelé « braise » et fixé sa taille à 48 avec une macro pour pouvoir changer facilement ce nombre dans tout le programme. C’est la déclaration suivante :

aaaaa#define NB_BRAISES 48
aaaaaint braise[NB_BRAISES] ;

L’initialisation de ce tableau est pour chacune de ses positions un nombre pris au hasard entre 0 et la taille de la ligne qui est la taille horizontale de l’écran définie par la macro ECRAN_X.
Avec ce tableau est également en global une ligne temporaire en mémoire nommée « tmp ». C’est un pointeur de caractères non signés. Il va servir de tableau après allocation de mémoire à la taille voulue. C’est la définition :
unsigned char* tmp ;
Pour l’affichage nous avons opté pour un double buffering, avec une image intermédiaire toujours en globale « page » ce qui donne la déclaration :

aaaaaBITMAP* page ;

Ainsi, avant d’entrer dans la boucle du processus d’animation, après la fonction « couleurs_flammes » pour la création de la palette, une fonction initialise le pointeur BITMAP *page, le pointeur unsigned char *tmp et le tableau d’entiers « braise » :

(1)aaaaaint initialisations(void)
aaaaa{
aaaaaint i;
(2)aaaaaaapage = create_bitmap(ECRAN_X, ECRAN_Y);
aaaaaaaaatmp = (unsigned char *)malloc(sizeof(unsigned char) * ECRAN_X);
aaaaaaaaaif (!tmp || !page) {
aaaaaaaaaaaaset_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
aaaaaaaaaaaaallegro_message("Pas assez de mémoire ?\n");
aaaaaaaaaaaareturn 0;
aaaaaaaaa}
aaaaaaaaaclear(page);

(3)aaaaaaafor (i=0; i<NB_BRAISES; i++)
aaaaaaaaaaaaabraise[i] = rand() % ECRAN_X;

(4)aaaaaaareturn 1;
aaaaa}

(1) La fonction « initialisations » n’a pas de paramètre et renvoie un entier.

(2) Création de la bitmap « page » à la dimension de l’écran. Ensuite allocation de mémoire pour l’équivalent d’une ligne de l’écran codé en 8 bits (ECRAN_X fois la taille d’un caractère non signé). Au cas où un problème de mémoire surviendrait, test pour vérifier le bon fonctionnement des deux opérations. Si un problème est survenu un message d’erreur est envoyé via l’appel d’une fonction spécialisée « allegro_message » et ensuite la valeur de retour 0 provoque la sortie de la fonction. S’il n’y a pas eu de problème toutes les positions de la bitmap « page » sont mises à 0 avec l’appel de la fonction « clear ».

(3) Le tableau « braise » est un tableau d’entiers qui correspondent à des points sélectionnés au hasard sur une ligne. Pour avoir une position horizontale de chacun il suffit de tirer un nombre pseudo-aléatoire avec la fonction « rand » puis exercer un modulo avec la taille maximum de la ligne ECRAN_X. Ensuite affecter ce nombre à l’entier de la i-ème position du tableau « braise ».

(4) Tout a bien fonctionné et la fonction renvoie à la fonction appelante la valeur 1.

3. Processus générateur

Programme complet, boucle du processus

Voici le détail de la fonction « main » :

aaaaaint main(void)
aaaaa{
aaaaaaaaaallegro_init( );
aaaaaaaaainstall_keyboard( );
aaaaaaaaaif (set_gfx_mode(GFX_AUTODETECT, ECRAN_X, ECRAN_Y, 0, 0) != 0){
aaaaaaaaaaaaaallegro_message("Erreur pour le mode graphique\n%s\n", allegro_error);
aaaaaaaaaaaaaallegro_exit();

aaaaaaaaaaaaareturn 1;
aaaaaaaaa}
(1)aaaaaacouleurs_flammes( );
aaaaaaaaaif ( ! initialisations( ) )
aaaaaaaaaaaareturn 1 ;

(2)aaaaaawhile ( ! keypressed ( ) ) {
(3)aaaaaaaaaligne_initiale_de_feu( );
(4)aaaaaaaaamonte_flammes( );
(5)aaaaaaaaablit(page, screen,0,0,0,0,ECRAN_X, ECRAN_Y);
aaaaaaaaa}
(6)aaaaaaafree( tmp);
aaaaaaaaadestroy_bitmap ( );
aaaaaaaaareturn 0;
aaaaa}
aaaaaEND_OF_MAIN( );


(1) Après quelques initialisations d’usage propres à l’utilisation de la librairie Allegro, se trouve l’appel de la fonction « couleurs_flammes » pour la création de la palette qui nous intéresse.
Ensuite test sur la valeur de retour de la fonction « initialisations ». Si c’est 1, c’est-à-dire si tout s’est bien déroulé, le test est faux et le bloc est sauté. Sinon, le return provoque la sortie du programme.

(2) Boucle « tant que » du processus. Le test est l’appel d’une fonction qui renvoie simplement si oui ( valeur 1 ) ou non (valeur 0 ) une touche du clavier a été pressée. Si oui, le test est faux et la boucle prend fin. Si non le test est vrai et la boucle continue.

(3) Appel à la fonction « ligne_initiale_de_feu ». Le rôle de cette fonction est de modifier la ligne de base à partir de laquelle les flammes sont constituées. Le détail de cette fonction est donné plus bas.

(4) Appel de la fonction « monte_flammes ». A partir du haut jusqu’en bas de l’écran, c’est la recopie de la ligne immédiate du bas à la lignes courante supérieures avec décrémentation progressive de la couleur.

Détail de la fonction « ligne initiale de feu »

Fonction pour établir et modifier la ligne de base sur laquelle s’appuie l’édifice des flammes

(1)aaaaavoid ligne_initiale_de_feu(void)
aaaaaaa{
aaaaaaaint i, dx;

(2)aaaaaaaafor ( i=0; i<ECRAN_X; i++ )
aaaaaaaaaaaaatmp[ i ] = 0;

(3)aaaaaaaafor ( i=0; i<NB_BRAISES; i++ ) {
(4)aaaaaaaaaa for ( dx = braise[ i ] – 20; dx < braise[ i ] + 20; dx++ )

(5)aaaaaaaaaaaaaaif ((dx >= 0) && (dx < ECRAN_X))
aaaaaaaaaaaaaaaaaaaatmp[ dx ] = MIN( tmp[ dx ] + 20 – ABS( braise[ i ] – dx ), 192 );

(6)aaaaaaaaaaaaaabraise[ i ] += (rand() & 7) - 3;
(7)aaaaaaaaaaaaaaif (braise[ i ] < 0)
aaaaaaaaaaaaaaaaaaabraise[ i ] += ECRAN_X;
aaaaaaaaaaaaaaaaelse
aaaaaaaaaaaaaaaaaaaif (braise[ i ] >= ECRAN_X)
aaaaaaaaaaaaaaaaaaaaaabraise[ i ] -= ECRAN_X;
aaaaaaaaaaa}
(8)aaaaaaaamemcpy(page->line[ECRAN_Y-1],tmp, sizeof(unsigned char)*ECRAN_X);
aaaaa}

(1) « ligne_initiale_de_feu » ne renvoie rien et ne prend pas d’argument.

(2) Toutes les positions désignées par le pointeur tmp sont mises à 0.

(3) et (4) Boucles for imbriquées. Tout d’abord en (3) chaque position du tableau « braise » va être convoquée via l’incrémentation de la variable i. Pour chaque indice i, braise[ i ] contient une valeur de position en x sur une ligne. Pour faire un segment il suffit de lui retrancher une valeur à gauche et de l’ajouter à droite. La taille du segment est fixée à 40 pixels.
En (4) l’objectif est de parcourir chaque segment. Pour ce faire la valeur de départ à gauche est récupérée dans la variable « dx » avec braise[ i ] – 20. Le test d’arrêt est effectué sur la valeur de fin du segment à droite avec braise[ i ]+20. Le segment est de -20 à 20 pixels à partir de la position braise[ i ] ; dx est incrémentée de un en un et parcourt toutes les positions du segment.

(5) Si la valeur de dx fait que dx est toujours dans la ligne et ne sort pas de l’écran, la position courante dans le segment va être affectée d’une valeur pour la couleur du pixel.

La formule pour obtenir cette valeur utilise la macro « MIN » qui donne la valeur minimum entre deux valeurs : MIN( a, b) donne a si a<b et b sinon.

Le deuxième argument de MIN est 192, le premier se décompose en trois parties :


aaaaa


aaaaa1) donne la distance de dx au centre du segment braise[ i ] en positif à gauche et négatif à droite, mais restitué en valeur absolue.
aaaaa2) Comme 20 est la distance maximum d’une extrémité au centre, en soustrayant la valeur absolue de la distance de dx au centre, aaaaaplus dx est près des bords plus le résultat est petit et plus dx est près du centre plus le résultat est grand. Le résultat va de 0 à 20 aaaaaet de 20 à 0.
aaaaa3) Uniquement pour le premier segment toutes les positions de tmp sont à 0. Mais ensuite des segments peuvent se superposer aaaaacomme nous l’avons indiqué plus haut. La valeur de chaque position de tmp est additionnée ce qui surélève d’autant les valeurs aaaaacalculées en 2), et crée un effet de superposition, comme des collines à partir du mouvement de crescendo et decrescendo.

aaaaaC’est-à-dire que l’augmentation de l’intensité de la couleur dépend du rapport entre le nombre de « braises » et la taille horizontale aaaaade l’écran. Le rapport est ici prévu à l’origine pour un écran tout petit de 320 pixels sur 200 pixels.

aaaaaSi le résultat final de ce calcul est inférieur à 192, il est affecté à la position dx de tmp, sinon tmp prend une valeur maximum de aaaaa192

(6) Ensuite la position du centre de segment braise[ i ] est légèrement modifiée au hasard dans la fourchette de – 3 à 3 pixels, afin de faire bouger la ligne et de créer l’animation souhaitée.

(7) L’écran est considéré comme circulaire, c’est-à-dire que si braise[ i ] dépasse à gauche de l’écran il est renvoyé à droite et inversement s’il dépasse à droite il est renvoyé à gauche de l’écran.

(8) La dernière instruction est la recopie de cette nouvelle ligne de base, en bas de l’écran, sur la bitmap intermédiaire « page ». Pour ce faire la fonction « memcpy », qui fait partie de la librairie standard du C <string.h>, est employée. Cette fonction copie à l’adresse du premier argument la taille de mémoire passée en troisième argument et à partir de l’adresse passée en deuxième argument. En l’occurrence la ligne tmp est recopiée toute entière à l’adresse de la dernière ligne de la bitmap « page ».

Détail de la fonction « monte_flammes »

Après création de la ligne de base il reste à faire monter la flamme. C’est le rôle de la fonction « monte_flammes »

(1)aaaaavoid monte_flammes ( void )
aaaaa{
aaaaaint y,x,c;

(2)aaaaaa afor ( y = 192; y < ECRAN_Y – 1; y++ )
aaaaaaaaaaaaaafor ( x = 0; x < ECRAN_X; x++ ) {
(3)aaaaaaaaaaaaaaac = page->line[ y+1 ][ x ];
aaaaaaaaaaaaaa aaac=( c > 0 ) ? - -c : 0;
aaaaaaaaaaaaa aaaapage->line[ y ][ x ] = c;
aaaaaaaaaa}
aaaaa}

(1) « monte_flammes » ne prend pas de paramètre et ne renvoie rien.

(2) Boucles for imbriquées. La première examine les lignes, la seconde les pixels de chaque ligne.

(3) Le principe est de partir à la hauteur maximum des flammes et de descendre ligne par ligne. A chaque ligne, il s’agit de copier la ligne de dessous ( y+1 ) sur la ligne courante y. Pour ce faire la valeur de couleur de chaque pixel de la ligne de dessous est récupérée dans la variable c. Si elle est supérieure à 0 elle est décrémentée de 1. La valeur c obtenue est affectée à la même position horizontale sur la ligne courante y. C’est ainsi que le dégradé vers le haut des flammes est obtenu. C’est également ce qui donne la hauteur maximum des flammes. La valeur de couleur est 192 au maximum et elle décroît à chaque ligne copiée de un en un jusqu’à 0.

Maintenant il est possible de faire traverser des flammes à un bonhomme, voire de le faire courir dans une boite enflammée ou au contraire de le faire s’enfuir à la vue des flammes, d’être poursuivi par des flammes. Les flammes peuvent aussi perdre leur caractère de flammes ordinaires et changer de couleur ce qui est assez amusant et puis il y a à voir l’étude de flammes sur une surface circulaire etc.