fxroto
Rotozoom
Sûrement que vous avez déjà vu cet effet sans en connaître le nom "officiel"... vous savez, quand une image est répétée partout sur l'écran, et que la caméra zoom et tourne en même temps. Rien de mieux pour vous donner un bon mal de cœur! Cet effet utilise deux principes déjà vu dans les tuteurs de la section 2D... le changement d'échelle d'un bitmap et la rotation 2D .La seule différence est qu'on devra répéter l'image sur l'écran pour la remplir complètement, et qu'on devra introduire quelques petites modifications.
ROTATION 2D
Je ne répéterai pas ici l'ensemble des explications déjà vu dans le chapitre 2 de la section 3D. Si on veux effectuer une rotation sur un point, on utilise les formules de rotations tel que je les ai démontrés auparavant:
x' = temp_x * cosinus(angle) - temp_y * sinus(angle)
y' = temp_x * sinus(angle) + temp_y * cosinus(angle)
Pour faire subir une rotation à un bitmap tout entier, il suffit de traiter individuellement chaque pixel du bitmap, pour tout l'abscisse et tout l’ordonnée, et de mettre à jour sa nouvelle position sur l'écran. A présent, il faut ajouter un facteur d'échelle, qui viendra modifier la taille du bitmap, évidemment sans toucher au bitmap original. Pour se faire, il suffit de multiplier chaques coordonnées par le facteur.
Par exemple, si on donne 1 comme facteur, les coordonnées x et y augmenterons de façon normale, c'est à dire 1 pixel à la fois, donnant au bitmap sa taille normale. Si on envoie .5, l'image sera grossit 2 fois, car pour chaque pixel sur le bitmap original, on en copie 2 sur l'écran. Finalement, si on envoie 2, l'image sera réduite de moitié, car pour chaque pixel sur l'écran, on en saute 2 dans le bitmap original. Regardons maintenant attentivement ce que pourrais ressembler notre boucle principale:
void rotozoom(float angle, float echelle)
{
float bx, by;
float cossc = cos(angle) * echelle;
float sinsc = sin(angle) * echelle;
unsigned int offset = 0;
Au début de la procédure, je calcule d'emblée cos(angle) et sin(angle) que je multiplie par le facteur "échelle", afin de gagner un peu de vitesse (plus sur ce sujet à la fin). Ensuite, la variable offset, qui contient la position sur l'écran, est mise à 0.
for (int y=0;y<200;y++)
{
for (int x=0;x<320;x++)
{
bx = (x * cossc) - (y * sinsc);
by = (x * sinsc) + (y * cossc);
virtuel[offset++] = bitmap[bx + ((int)(by)<<8)];
}
}
}
La boucle principale parcours l'écran en entier. Ensuite, pour chaque coordonnée d'écran, on effectue la rotation/zoom sur le bitmap en mémoire, et finalement on copie le pixel (bx,by) résultant sur l'écran. Nous avons besoin de typecaster (int)by pour multiplier par 256. Sur ce sujet, il faut noter que le format carré de 256x256 comme bitmap n'est pas dû au hasard. En effet, ce format est disposé de tel façon en mémoire que si la coordonnée (bx,by) est, par exemple, (-128,-128), la position sera quand même exacte car en adressant cette coordonnée on "fera le tour" du bitmap et on arrive quand même au pixel exact. Pour modifier ce rotozoomer pour accepter d'autres format, il aurait fallu s'assurer que les coordonnées ne soient jamais inférieure à 0 et supérieur au maximum de résolution du bitmap, ce qui aurait encore ralenti d'avantage le rotozoomer.
En parlant de vitesse, vous remarquerez que ce rotozoomer est terriblement lent. C'est que j'ai opter pour le code le plus simple possible, sans utiliser de mathématique à virgule fixe. Il est cependant à noter que le compilateur Turbo C++ 16-bit ne fait rien pour aider le CPU. Par exemple, DJGPP utilise le Floating Point Unit ( FPU )du Pentium, utilise des opcodes assembleur 32-bit tel MOVSD, et STOSD, optimise le code par plusieurs autres trucs que le dinosaure qu'est Turbo C++ ne connaît pas. Donc il existe 2 choix: ou bien optimiser ce rotozoomer pour plus de vitesse avec des mathématiques à virgule fixe (écrivez-moi si vous voulez de l'aide), ou changer carrément de compilateur.
CONCLUSION
Somme toute, cet effet est quand même très simple à réaliser avec les formules de rotations de base. Dernier petit détail : vous avez peut être remarquer que je n'effectue pas de translation (pour centrer ma rotation sur l'origine de l'écran à 160,100). Donc la rotation est effectuée sur 0,0. Cela ne change pas grand chose à l'effet, mais vous pouvez facilement rajouter aux équation de rotation une translation sur le centre de l'écran.
fxroto.cpp
//----------------------------------------------------------------------//
// FICHIER : FXBUMP.CPP //
// AUTEUR : Shaun Dore //
// DESCRIPTION : Rotozoom //
// DATE DE MODIFICATION : 12-01-99 //
// COMPILATEUR : Borland Turbo C++ Real Mode 16-bit compiler //
// NOTES : Compiler avec modele memoire Compact //
//----------------------------------------------------------------------//
#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,0000)
char *ecran = (char *) (0xA0000000L);
// Pointeur sur memoire tampon video
char *virtuel = new unsigned char[64000L];
// Pointeur sur image bitmap
// J'ai du utiliser ici farmalloc car l'operateur new ne semble pas etre
// capable de reserver un bloc de plus de 64k(65535), donc farmalloc nous
// permet d'allouer de la memoire dans le tas externe au segment de donnees
// (far heap).
char *bitmap = (unsigned char*) farmalloc(65536L);
// 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
bitmap[index++] = data;
}
else bitmap[index++] = data; // Sinon il n'est pas compresse...
} while(index < taille); // Tant qu'on a pas terminer de decoder
fclose(pcxfile);
return 1;
}
// Calcul le vecteur de rotation pour chaque pixel sur l'ecran
void rotozoom(float echelle, float angle)
{
float bx, by; // bitmap x,y pos
float cossc = cos(angle) * echelle; // calcule d'avance le vecteur de
float sinsc = sin(angle) * echelle; // rotation et multiplie par l'echelle
float ysin, ycos; // pour optimiser la vitesse
unsigned int offset = 0; // offset dans le buffer
for (int y=0;y<200;y++)
{
ysin = y * sinsc; // Eviter de recalculer y
ycos = y * cossc;
for (int x=0;x<320;x++)
{
bx = x * cossc - ysin; // Formule de rotation 2D
by = x * sinsc + ycos;
virtuel[offset++] = bitmap[bx + ((int)by<<8)]; // Affiche le pixel
} // correspondant dans
} // le double buffer
}
void main()
{
unsigned char angle=0;
float CosTable[256];
char fichier[] = "fxroto.pcx";
for(int i=0; i<256; i++) CosTable[i]=cos(i*M_PI/128); // Table precalculer
asm {mov ax,0x13;int 0x10} // Mode 13h
if(!loadpcx(fichier, 65536L)) // Charger l'image
{
asm {mov ax,0x03;int 0x10} // Erreur!
printf("ERREUR: Incapable de charger %s !\n", fichier);
}
else
{
while(!(kbhit()))
{
rotozoom(CosTable[angle]*5,CosTable[angle]*2*M_PI);
while (!(inp(0x3DA) & 8));
memcpy(ecran,virtuel,64000L); // Copie le contenu du buffer
angle++; // Incremente l'angle de rotation
}
asm {mov ax,0x03;int 0x10}
}
delete []virtuel; // Libere la memoire pour le buffer
farfree(bitmap); // ainsi que pour l'image
}