fxtunnel
Tunnel de points
Parmis les effets graphiques les plus anciens, on retrouve celui du tunnel de points (dot tunnel), qui est très simple à réaliser, si on procède de la bonne façon. En cherchant de l'information sur cet effet il y a quelques temps, j'ai trouvé des programmes qui utilisaient des équations 3D et autres trucs beaucoup trop complexe pour un effet si simple. En fait, quand on regarde un tel effet (par exemple, dans le classique Second Reality de FC), on remarque bien entendu qu'il s'agit d'un effet d'optique. En réalité, il s'agit de plusieurs cercles, les plus "loin" étant plus petit et plus sombre, et les "proche" ont un rayon plus grand et une couleur plus claire.
"La lumière au bout du tunnel..."
Dans la version complexe et 3D de cet effet, on utilise la même technique qu'un champs d'étoiles (voir chapitre 1, section 3D), sauf qu'on génère les points en les disposants sur un cercle (voir chapitre 2, section 2D, et exemple A, chapitre 2, section 3D). Donc avec cette version, on a besoin des formules de projections et tout le reste. Mais dans la version simple et directe, on utilise cet algorithme:
1 -Rayon du cercle intial petit et couleur sombre.
2 -Dessiner le cercle.
3 -Déplacer légèrement l'origine du cecle selon le mouvement désiré.
4- Augmenter le rayon et la clarté de la couleur.
5- Repéter le 2 tant que le rayon n'est pas aussi grand que l'écran
Par "cercle", j'entend évidemment une série de point disposé sur l'équation du cercle, par exemple, on dessine 1 point à tous les 6 degré, sur un cercle de 360 degré cela donne 60 points. Afin de bien comprendre cet algorithme, supposons pour un instant que nous oublions l'étape #3, et qu'on dessine une série de cercles concentriques selon cet algorithme, voici ce que nous obtenons à l'écran:
Pas très convaincant comme tunnel... c'est parce qu'étant donné que l'origine des cercles ne change pas, l'effet d'optique est perdu. C'est un mouvement fluide des cercles qui va venir donner cette impression de tunnel. Pour se faire, il faut déplacer l'origine d'une façon fluide, donc sinusoïdale. Donc, revenons, à l'étape #1, soit initialiser le tunnel:
char x = 0; // Centre x du tunnel (-128..127)
char y = 0; // Centre y du tunnel (-128..127)
unsigned int z; // "Profondeur" du tunnel, aussi le rayon du cercle
unsigned int angle; // Compteur d'angle dans le cercle etant dessiner
unsigned char inc_dist ; // Incrementeur de distance entre les cercles
unsigned char col; // Couleur d'un point
Les coordonnées x et y sont ajusté pour tenir compte du centre de l'écran, donc on considère 160,100 comme 0,0. La variable z correspond au rayon du cercle, et c'est le compteur qui nous permet d'arrêter de dessiner les cercles quand on est rendu au rayon maximum. La variable inc_dist aide à l'effet dans le sens où plus les cercles ont un grand rayon, plus la distance entre eux est grande. Pour dessiner notre cercle, on utilise l'équation paramétrique du cercle:
X = CentreX + Rayon * Cos(theta)
Y = CentreY + Rayon * Sin(theta)
À présent, pour déterminer le mouvement, je déplace le cercle selon courbes sinusoïdales, qui seront naturellement précalculée dans des tables:
CosMouv[i] = (cos(M_PI*i/128)*AMPLITUDE_X);
SinMouv[i] = (sin(M_PI*i/128)*AMPLITUDE_Y);
AMPLITUDE détermine les extrêmes des mouvements des cercles, c'est à dire que les cercles se déplaceront entre l'intervalle [-amplitude, +amplitude]. Maintenant que tout cela est mis sur pied, il nous reste à examiner la boucle principale:
do // Boucle principale
{
memset(buffer,0,64000);
col = 16;
z = 30;
inc_dist = 2;
Au début de la boucle, on initialise le premier cercle, avec une couleur foncée (16, sur la palette VGA standard), une profondeur (rayon) de 30 et la distance entre chaque cercle (l'incrémenteur de rayon) est de 2.
do // Boucle pour une frame
{
angle=0; // Le cercle commence a etre dessine a 0 deg
Cette boucle dessinera l'ensemble des cercles, et donc a chaque nouveau cercle le compteur d'angle du cercle est remis à 0.
do // Boucle pour un cercle
{
DessinePoint(CosMouv[(x+(PROFONDEUR-z))%256],
SinMouv[(y+(PROFONDEUR-z))%256],
z,angle,col);
angle+=PRECISION;
} while (angle < 360);
Finalement, cette boucle dessine un cercle, un point à la fois. DessinePoint reçoit l'origine x,y d'un cercle, ainsi que le rayon z, l'angle et la couleur. Comme mentionné plus haut, on utilise CosMouv et SinMouv afin de donner un mouvement fluide, et adresse la table en ajustant selon où nous somme rendu dans le tunnel, c'est-à-dire que PROFONDEUR étant la "profondeur" maximale du tunnel, et z étant le rayon du cercle courant, on soustrait z de profondeur pour trouver l'indice approprié dans le table, pour donner le mouvement désiré à notre tunnel.
if (((z += inc_dist) % DENSITE) == 0) inc_dist++;
if ((++col)>31) col = 31;
} while (z<PROFONDEUR);
Après avoir dessiner un cercle, on vérifie s'il y a lieu d'incrémenter la distance entre les cercles, et on augmente la couleur si on ne dépasse pas la plus brillante, 31 sur la palette VGA standard. Tout cela continue tant que le rayon du cercle n'a pas atteint la profondeur maximale du tunnel (en général, l'écran au complet, donc 200).
x += MOUV_X;
y += MOUV_Y;
while ( inp(0x3DA) & 8);
while (!(inp(0x3DA) & 8));
memcpy(screen,buffer,64000);
} while (!(kbhit()));
À présent, il ne nous reste plus qu'a déplacer l'origine x,y du tunnel selon le déplacement voulu, d'attendre une retrace d'écran et de copier le tout de la mémoire tampon sur l'écran principal, et de recommencer au début pour dessiner une nouvelle frame!
Conclusion
Il existe plusieurs variantes pour cet effet, on pourrait par exemple créer plus qu'un seul tunnel dans un même écran, remplacer les pixels par des petit bitmaps, ou je ne sais quoi encore. Dans le code source, j'ai très bien commenté le tout et j'ai ajouter une panoplie de constante #DEFINE que vous pouvez modifier pour changer dramatiquement l'apparence du tunnel. Je ne garanti pas qu'elles fonctionnent toutes, mais certaines donnent des effets très intéressant.
fxtunnel.cpp
//--------------------------------------------------------------------------//
// D O T T U N N E L //
// //
// code: Shaun Dore(dores@videotron.ca) //
// note: Modele memoire Compact //
// //
//--------------------------------------------------------------------------//
#include <mem.h> // memcpy, memset
#include <math.h> // sin, cos
#include <conio.h> // getch, kbhit
#include <stdio.h> // printf
// J'avoue que je suis virer un peu malade avec les macros et les #defines
// mais j'ai tellement changer les parametres souvent que je me suis fatiguer
// de toujours les reecrires. Je ne garanti rien sur leur fonctionnement
// mais elle permette de changer dramatiquement l'appparence du tunnel.
// Utilisez-les a vos risques!
#define PRECISION 6 // Incrementeur d'angle dans le cercle
#define MOUV_X 3 // Incremente le mouvement du tunnel sur l'axe X
#define MOUV_Y 4 // Incremente le mouvement du tunnel sur l'axe Y
#define DENSITE 10 // Distance entre les cercles du tunnel
#define AMPLITUDE_X 60 // Amplitude du deplacement X
#define AMPLITUDE_Y 30 // Amplitude du deplacement Y
#define PROFONDEUR 200 // Profondeur du tunnel
#define ANGLE_MAX 360 // Angle du cercle
#define RESOLUTION_X 320 // Resolution horizontale
#define RESOLUTION_Y 200 // Resolution verticale
#define MILLIEU_X RESOLUTION_X/2 // Centre de l'ecran X
#define MILLIEU_Y RESOLUTION_Y/2 // Centre de l'ecran Y
// Pointeur sur memoire video
unsigned char far *screen = (char far*) (0xA0000000L);
// Pointeur sur memoire tampon d'ecran
unsigned char far *buffer = new unsigned char[RESOLUTION_X*RESOLUTION_Y];
float CosTable[ANGLE_MAX]; // Table des cosinus precalcule
float SinTable[ANGLE_MAX]; // Table des sinus precalcule
int CosMouv[256]; // Table precaucule pour les mouvements du tunnel (X)
int SinMouv[256]; // Table precalcule pour les mouvements du tunnel (Y)
// PreCalcul()
// Calcule les 4 tables pour acceler les calculs
// - 2 tables pour le dessin du cercle (SinMouv et CosMouv)
// - 2 tables pour le deplacement du tunnel (SinTable et CosTable)
void PreCalcul()
{
// Le tunnel va se deplacer selon AMPLITUDE_X et AMPLITUDE_Y, soit entre
// l'intervalle [-AMPLITUDE_X..AMPLITUDE_X], et la meme chose pour Y
for(int i=0;i<256;i++)
{
CosMouv[i] = (cos(M_PI*i/128)*AMPLITUDE_X);
SinMouv[i] = (sin(M_PI*i/128)*AMPLITUDE_Y);
}
// Cette table sert a dessiner les points sur un cercle de ANGLE_MAX deg
// Pour chaque angle du cercle, 2*PI*angle/360 (360 = angle du cercle)
for (i=0;i<ANGLE_MAX;i++)
{
CosTable[i] = (cos(2*M_PI*i/ANGLE_MAX));
SinTable[i] = (sin(2*M_PI*i/ANGLE_MAX));
}
}
// DessinePoint()
// Cette fonction dessine un point, dispose sur un cercle. On envoie l'angle
// le rayon et la couleur du point, et on utilise la formule du cercle:
// - X = CentreX + Rayon * Cos(theta)
// - Y = CentreY + Rayon * Sin(theta)
// Notre cercle comporte ANGLE_MAX degree.
void DessinePoint(unsigned int x_centre, unsigned int y_centre,
unsigned int rayon, unsigned int angle, unsigned char col)
{
unsigned int x = MILLIEU_X+x_centre+(rayon*CosTable[angle]);
unsigned int y = MILLIEU_Y+y_centre+(rayon*SinTable[angle]);
if ((x<RESOLUTION_X) && (y<RESOLUTION_Y)) buffer[(y*RESOLUTION_X)+x] = col;
}
// Tunnel()
// Dessine une serie cercle, former par des points, de rayon croissant.
// Les cercles debutent avec un petit rayon qui augmente graduellement,
// ainsi que leur indice de couleur, a partir de 16 jusqu'a 31, un degrade
// de gris sur la palette VGA standard. Chaque cercle suit le deplacement
// prescrit par CosMouv et SinMouv, qui s'assure que le tunnel se deplacera
// de facon sinuosidale.
void Tunnel()
{
char x = 0; // Centre x du tunnel (-128..127)
char y = 0; // Centre y du tunnel (-128..127)
unsigned int z; // "Profondeur" du tunnel, aussi le rayon du cercle
unsigned int angle; // Compteur d'angle dans le cercle etant dessiner
unsigned char inc_dist; // Incrementeur de distance entre les cercles
unsigned char col; // Couleur d'un point
do // Boucle principale
{
memset(buffer,0,64000L); // Vide le double buffer
col = 16; // La couleur de base est 16
z = 30; // Prodonfeur initiale a 30 (rayon du cercle #1)
inc_dist = 2; // Les cercles sont espace de 2 au debut
do // Boucle pour une frame
{
angle=0; // Le cercle commence a etre dessine a 0 deg
do // Boucle pour un cercle
{
DessinePoint(CosMouv[(x+(PROFONDEUR-z))%256], // Dessine un point sur
SinMouv[(y+(PROFONDEUR-z))%256], // le cercle, a angle,
z,angle,col); // et ramene les valeurs
angle+=PRECISION; // entre 0..255 avec %
} while (angle < ANGLE_MAX); // tant que < ANGLE_MAX
if (((z += inc_dist) % DENSITE) == 0) inc_dist++;
// Incremente la distance
if ((++col)>31) col = 31; // entre les cercles et
} while (z<PROFONDEUR+20); // la couleur tant que
// pas PROFONDEUR max
x += MOUV_X; // Deplace le tunnel sur l'axe X
y += MOUV_Y; // Deplace le tunnel sur l'axe Y
while (!(inp(0x3DA) & 8)); // et le debut d'une autre!
memcpy(screen,buffer,64000L); // Copie le resulat sur l'ecran
} while (!(kbhit())); // tant qu'une touche pas enfoncer
delete[] buffer; // Efface le buffer de 64k
}
void main()
{
asm{mov ax,0x13;int 0x10} // Mode graphique
PreCalcul(); // Table precacule
Tunnel(); // Tunnel de points
asm{mov ax,0x03;int 0x10} // Retour au mode texte
}