fxtunmap
Tunnel avec texture
L’exemple du tunnel de point que j’avais présenté il y a quelques temps était très simpliste. Nous avions vaguement l’impression d’avancer dans un tunnel. Si on collait une texture sur les parois de ce même tunnel, on aurait du même coup un résultat beaucoup plus réaliste. C’est exactement le sujet de ce tuteur.
Texture mapping circulaire
L’effet présenté ici est aussi connu sous le nom de feedback (sp?). En effet, nous sommes libres de tenir compte de la perspective ou non. Dans le cas du tunnel, on applique un facteur de distance pour modifier l’apparence des pixels (plus loin = plus petit, plus proche = plus gros…), dans l’autre cas on obtient un résultat très intéressant, on les couleurs semblent tournoyer dans un vortex. En voici un exemple :
Le principe du texture mapping est de trouvé, pour chaque pixel X,Y sur l’écran, le pixel X,Y dans la texture. Ces pixels dans la texture (ou textels) sont appelé, pour éviter la confusion, U et V. Nous allons donc devoir appliqué notre texture de façon circulaire sur l’écran. Comme nous l’avons déjà vu, une texture de 256x256 nous simplifie grandement la vie, nous permettant ainsi de controler facilement les débordement (avec un unsigned char).
Sans entrer dans les détails, voici la formule pour faire le texture mapping circulaire :
U = sqrt( x*x + y*y )
V = atan2(y,x)
Nous reconnaissons ici le théorème de Pythagore, qui nous permet de calculer la distance de l’origine dans la texture map (256x256), et arctangente qui retourne l’angle par rapport à l’origine. À partir de ces deux informations, nous avons le textel U,V que l’on doit afficher sur l’écran, à la position X,Y :
Pour réaliser le feedback, nous n’avons qu’a parcourir les pixels sur l’écran, et pour chacun d’entre eux, trouver le U,V correspondant. Cependant, ce processus est extrêmement lent (sqrt et atan2). Nous allons donc devoir utiliser les tables précalculer pour chaques pixels, donc 2 tables de 320x200.
char *tab_rayon = new unsigned char[64000L];
char *tab_angle = new unsigned char[64000L];
Nous commençons par remplir ces tables. Ensuite, dans la boucle principale, nous allons appelé la fonction qui calculera la coordonnées U,V :
void calc_tables()
{
unsigned int offset=0;
for (int y=-100; y<100; y++)
for (int x=-160; x<160; x++)
{
offset= (x+160)+((y+100) * 320);
tab_rayon[offset] = (sqrt( x*x + y*y ));
tab_angle[offset] = (atan2(y,x) * 256 / M_PI / 2);
}
}
Vous remarquerez que atan2 retourne l’angle en radian. Nous allons donc devoir le convertir en degré par la formule degré = radian*360/pi/2. Mais comme j’utilise un système trigonométrique a 256 degré, on multipliera alors par 256. Maintenant, il suffit de dessiner le pixel a partir de ses coordonnées U,V :
void dessine_tunnel(unsigned char r, unsigned char a)
{
unsigned int offset;
unsigned char rayon, angle;
for (int offset=0;offset<160;offset++)
{
rayon = r + tab_rayon[offset];
angle = a + tab_angle[offset];
virtuel[offset] = texture[(rayon<<8) + angle];
}
}
On utilise alors la même formule, soit y*largeur+x, donc y*256+x, qui se bit shift aussi très facilement (<<8). Pour animer l’image, on utilise l’offset r(ayon) et a(ngle). Nous obtenons un effet de feedback très beau, qui est parfait pour des arrière-plans (j’en utilise quelques uns dans mon demo Hypodermik).
Tunnel avec Texture mapping circulaire
Il est très facile de transformer le feedback en tunnel. Pour se faire, il suffit de tenir compte de la perspective, c’est à dire qu’il faut que les pixels proches de l’origine (160,100) soient plus petits (car ils sont plus lointains), et que ceux qui sont près des bordures de l’écrans soient plus gros (car plus proches). En se servant du rayon comme référence, il suffit de diviser un facteur de distance par ce dernier :
tab_rayon[(x+160)+((y+100)*320)] = DISTANCE/(sqrt( x*x + y*y ));
Eh voilà! Vous obtenez alors un tunnel texture-mappé!
Notes sur le code source :
Veillez noter que j’ai dû faire quelques petits compromis dans le code source afin que le programme s’exécute comme il faut sur un compilateur 16-bit. Turbo C++ semble avoir de la misère avec les nombres trop près de 0, ce qui cause des divisions par 0. J’utilise donc un facteur EPSILON, qui empêche de programme de planter.
Tunnel.cpp
//----------------------------------------------------------------------//
// FICHIER : TUNNEL.CPP //
// AUTEUR : Shaun Dore //
// DESCRIPTION : Tunnel avec texture //
// DATE DE MODIFICATION : 07-06-99 //
// COMPILATEUR : Borland Turbo C++ Real Mode 16-bit compiler //
// NOTES : Compiler avec modele memoire Compact //
//----------------------------------------------------------------------//
#define DISTANCE 6000 // Perspective
#define EPSILON .0000000000001 // Evite floating point error (compilateur 16-bit)
#include <mem.h> // memset, memcpy
#include <math.h> // cos, sin, M_PI
#include <stdio.h> // fread, printf
#include <conio.h> // getch, kbhit
#include <alloc.h> // farmalloc
// Pointeur sur adresse de la memoire video (A000:0000h)
char *ecran = (char *) (0xA0000000L);
// Pointeur sur memoire tampon video
char *virtuel = new unsigned char[64000L];
// Pointeur sur texture map (256x256)
char *texture = (unsigned char*) farmalloc(65536L);
// Tableau pre-calculer des rayons
char *tab_rayon = new unsigned char[64000L];
// Tableau pre-calculer des angles
char *tab_angle = new unsigned char[64000L];
// Fixe la palette VGA correspondant a l'image chargee en memoire
void setpal(unsigned char col,unsigned char r,unsigned char g,unsigned char b)
{
outp (0x03C8,col);
outp (0x03C9,r);
outp (0x03C9,g);
outp (0x03C9,b);
}
// Decode un fichier PCX encoder Run Length Encoding
int loadpcx(char *nomfich, unsigned long taille)
{
unsigned char data, nb_octets, palette[768];
unsigned long index = 0;
unsigned int index_rle;
FILE *pcxfile;
if (!(pcxfile = fopen(nomfich, "rb"))) return 0;
fseek(pcxfile, -768, SEEK_END);
fread(&palette, 768, 1, pcxfile);
for (int col=0;col<=255;col++)
setpal(col,palette[col*3]>>2,palette[col*3+1]>>2,palette[col*3+2]>>2);
fseek(pcxfile, 128, SEEK_SET);
do
{
fread(&data, 1, 1, pcxfile);
if ((data & 0xC0) == 0xC0) // Verifie si l'octet est compresse, avec les
{ // 2 premier bit (11000000). Si oui, les
nb_octets = (data & 0x3F); // 6 derniers bit devienne une boucle contenant
fread(&data, 1, 1, pcxfile); // le nombre de fois qu'il faut repeter
for (index_rle=1; index_rle<=nb_octets; index_rle++) // l'octet
texture[index++] = data;
}
else texture[index++] = data; // Sinon il n'est pas compresse...
} while(index < taille); // Tant qu'on a pas terminer de decoder
fclose(pcxfile);
return 1;
}
// Calcule le rayon et l'angle pour chaque pixels sur l'ecran
void calc_tables()
{
unsigned int offset=0;
for (int y=-80; y<80; y++)
for (int x=-160; x<160; x++)
{
offset= (x+160)+((y+80) * 320);
tab_rayon[offset] = (EPSILON+sqrt( x*x + y*y ));
if (x!=0 && y != 0)
tab_angle[offset] = (atan2(y,x) * 256 / M_PI / 2);
else
tab_angle[offset] = (atan2(y+EPSILON,x+EPSILON) * 256 / M_PI / 2);
}
/*
On calcule la distance du pixel de l'origine (160,100) par Pythagore
sqrt(x^2+y^2), on obtient la table des rayons possibles. Atan2(x,y)
retourne l'angle du pixel par rapport a l'origine et on converti de
radians vers degree par 360/pi/2 (256 dans notre cas)
*/
}
// Dessine l'effet
void dessine_tunnel(unsigned char r, unsigned char a)
{
unsigned int yoff;
unsigned char rayon, angle;
for (int y=0;y<160;y++)
{
yoff = (y<<8)+(y<<6);
for (int x=0;x<320;x++)
{
rayon = r+tab_rayon[yoff+x];
angle = a+tab_angle[yoff+x];
virtuel[yoff+6400+x] = texture[(rayon<<8) + angle];
}
}
/*
Dessine la texture sur l'ecran, en utilisant le rayon et l'angle
pour determiner le textel. r et a servent a deplacer la texture.
*/
}
void main()
{
unsigned char angle=0, rayon=0;
float CosTable[256];
char fichier[] = "texture.pcx";
for(int i=0; i<256; i++) CosTable[i]=cos(i*M_PI/128); // Table precalculer
printf("Calcul des tables . . .\n");
calc_tables();
memset(virtuel,0,64000L);
asm {mov ax,0x13;int 0x10} // Mode 13h
if(!loadpcx(fichier, 65536L)) // Charger texture
{
asm {mov ax,0x03;int 0x10} // Erreur!
printf("ERREUR: Incapable de charger %s !\n", fichier);
}
else
{
while(!(kbhit()))
{
dessine_tunnel(angle++,rayon++);
memcpy(ecran,virtuel,64000L); // Copie le contenu du buffer
}
getch();
for (int y=-80; y<80; y++)
for (int x=-160; x<160; x++)
{
tab_rayon[(x+160)+((y+80) * 320)] = DISTANCE/(EPSILON+sqrt( x*x + y*y ));
}
while(!(kbhit()))
{
dessine_tunnel(angle--,rayon++);
memcpy(ecran,virtuel,64000L); // Copie le contenu du buffer
}
asm {mov ax,0x03;int 0x10}
}
delete []virtuel; // Libere la memoire pour le buffer
delete []tab_angle;
delete []tab_rayon;
farfree(texture); // ainsi que pour l'image
}