Algorythme d'effet fraphique 2D de flammes, utilisant l'interface DirectDraw en plein ecran.
Source / Exemple :
//Compilé avec Borland C++ Builder 4
/*----------------------------------------------------------------------------*
| |
| Dessiner des Flammes, Unité principale (Feu.cpp) |
| |
| programmé par Blustuff |
| |
- ----------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------*
| Includes |
- ----------------------------------------------------------------------------*/
#include <windows.h>
#include "affichage.cpp"
//Uniquement pour Borland (Les lignes suivantes permette la liaision des librairies DirectDraw) :
#include <condefs.h>
USELIB("..\mssdk\lib\borland\dxguid.lib");
USELIB("..\mssdk\lib\borland\ddraw.lib");
/*----------------------------------------------------------------------------*
| Variables Globales |
- ----------------------------------------------------------------------------*/
bool FinDeProgramme = false;
/*----------------------------------------------------------------------------*
| Fonction WindowProc |
- ----------------------------------------------------------------------------*/
#pragma argsused //La fonction suivante n'utilise pas tous ses parametres
LRESULT CALLBACK WindowProc(HWND hWnd, unsigned uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_KEYDOWN :
if (wParam == VK_ESCAPE)
FinDeProgramme = true; //Quitter le programme lors de l'appui sur escape
break;
default : return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
/*----------------------------------------------------------------------------*
| Initialisation de la fenêtre |
- ----------------------------------------------------------------------------*/
bool InitialisationFenetre(HINSTANCE hInstance, HWND* hWnd)
{
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = (WNDPROC) WindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = sizeof(DWORD);
wc.hInstance = hInstance;
wc.hIcon = NULL;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = "XmplHr17Class";
if (!RegisterClass(&wc))
return false;
if (!(*hWnd = CreateWindow("XmplHr17Class", "Line Race", WS_VISIBLE|WS_POPUP, 0, 0, 0, 0, NULL, NULL, hInstance, NULL)))
return false;
ShowCursor(false); //Cacher le curseur de la souris
return true;
}
/*----------------------------------------------------------------------------*
| Fonction WinMain |
- ----------------------------------------------------------------------------*/
#pragma argsused //La fonction suivante n'utilise pas tous ses parametres
WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int)
{
HWND hWnd;
MSG msg;
if (!InitialisationFenetre(hInstance, &hWnd))
return 0;
if (!InitialiseAffichage(hWnd))
FinDeProgramme = true;
AfficherTexte(SurfaceTexte, "Blustuff Blue Fire", 0, 0, false, 0x00ff0000); //Afficher le texte sur la surface de texte
while (!FinDeProgramme)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
DessinerFlammes(); //Dessiner les flammes
RECT Destination = {0,0,200,50};
SurfaceSecondaire->BltFast(40, 130, SurfaceTexte, &Destination, DDBLTFAST_SRCCOLORKEY);//Ecire le texte
SurfacePrimaire->Flip(0,DDFLIP_DONOTWAIT); //Afficher la surface
}
}
DesallocationAffichage();
return 0;
}
/*----------------------------------------------------------------------------*
| |
| Dessiner des Flammes, Unité d'affichage V.1.0 (Affichage.cpp) |
| |
| programmé par Blustuff |
| |
- ----------------------------------------------------------------------------*/
/*
Note du programmeur :
- Ce programme utilise DirectDraw pour initialiser l'affichage, mais accede
par ses propres fonctions a la mémore vidéo pour la modifier. La connaissance
de DirectDraw n'est pas importante, la fonction DessinerFlamme et la seulle
qui puisse etre interessante ici.
- Ce programme n'est pas entièrement optimisé en rapidité. Cela pour diverse
raison (j'ai la flemme), dont la lisibilité du code qui doit rester a peu près
acceptable
- Note lexicale : Une interface est une structure de base de DirectX, dont
chaque autre élément,DirectDraw, DirectSound, DirectInput etc. hérite. Elle
possède notament la fonction Release() qui permet de désallouer la mémoire qui
lui était réservée.
Une Surface est un emplacement en mémoire contenant des
données susceptible d'etre affichée, a l'ecran ou sur une autre surface.
- J'ai horreur des termes en anglais dans une programme, parce que j'aime bien
rapeller que les francais ont réalisé les plus beaux programme jusqu'a
maintenant. Si j'ai laissé des noms de variables en anglais, veuillez m'en
excuser. (Celles de DX j'y suis pour rien)
- Je n'ai pas tout inventé tout seul. J'ai repris cet algorythme de Franck
Bauquier (defcon1@caramail.com) sur son site
http://defcon1.free.fr/ . J'ai
juste personalisé les flammes et adapté le programme pour DirectDraw
- Les flammes sont bleues. Ca simplifie vraiment le programme. Si vous voulez
faire des flammes avec autre chose, il faut revoir une bonne partie du code.
Les flammes doivent etre traitées couleur par couleur. Si vous voulez faire des
flammes violettes, vous devez traiter le rouge et le bleu séparement. Mon
programme ne s'y adapte pas (Et puis j'aime bien le bleu).
Pour me contacter :
blustuff@wanadoo.fr
http://perso.wanadoo.fr/blustuff/
/*----------------------------------------------------------------------------*
| Fichier d'en tête |
- ----------------------------------------------------------------------------*/
#include "affichage.h"
/*----------------------------------------------------------------------------*
| Initialisation |
- ----------------------------------------------------------------------------*/
/*
N'essayez pas trop de vous lancer dans la comprehension du code de
l'initialisation graphique de DirectDraw si vous n'avez jamais programmé avec
DirectX. J'appelle ici de nombreuse fonctions que je n'expliquerai pas
bool InitialiseAffichage(HWND hWnd)
{
DDSURFACEDESC2 ddsd;
DDSCAPS2 ddscaps;
//Creation de l'interface DirectDraw
if (DirectDrawCreateEx(NULL, (void**)&InterfaceDirectDraw, IID_IDirectDraw7, NULL) != DD_OK)
return false;
//Selection du niveau cooperatif
if (InterfaceDirectDraw->SetCooperativeLevel(hWnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN | DDSCL_ALLOWREBOOT) != DD_OK)
return false;
//Choix du mode d'affichage
if (InterfaceDirectDraw->SetDisplayMode(LargeurEcran, HauteurEcran, 32, 0, 0) != DD_OK)
return false;
//Creation des surfaces primaires et secondaires
ZeroMemory(&ddsd, sizeof(ddsd));
ZeroMemory(&ddscaps, sizeof(ddscaps));
ddsd.dwSize = sizeof( ddsd );
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX;
ddsd.dwBackBufferCount = 1;
ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
if (InterfaceDirectDraw->CreateSurface(&ddsd, &SurfacePrimaire, NULL) != DD_OK)
return false;
//Association du back Buffer
if (SurfacePrimaire->GetAttachedSurface(&ddscaps, &SurfaceSecondaire) != DD_OK)
return false;
//Effacement des surfaces
EffacerSurface(SurfacePrimaire);
EffacerSurface(SurfaceSecondaire);
//Création et effacement d'une surface réservée au texte
ZeroMemory(&ddsd,sizeof(ddsd));
ddsd.dwSize = sizeof(DDSURFACEDESC2);
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT ;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
ddsd.dwWidth = 200;
ddsd.dwHeight = 50;
InterfaceDirectDraw->CreateSurface(&ddsd, &SurfaceTexte, NULL);
DDCOLORKEY key;
key.dwColorSpaceLowValue = 0x00000000;
key.dwColorSpaceHighValue = 0x00000000;
SurfaceTexte->SetColorKey(DDCKEY_SRCBLT, &key);
EffacerSurface(SurfaceTexte);
//Stockage dans un tableau des valeurs en octet pour le débur de chaque ligne de l'ecran
SurfacePrimaire->Lock(NULL, &ddsd, DDLOCK_WAIT, NULL); //Obtention des propriétés de la surface
SurfacePrimaire->Unlock(NULL);
AlignY = (int*) malloc(ddsd.dwHeight * sizeof(int));
for (unsigned int y = 0 ; y < ddsd.dwHeight ; y++)
AlignY[y] = ddsd.lPitch * y / 4;
//La fonction retourne true si l'initialisation réussit
return true;
}
/*----------------------------------------------------------------------------*
| Desallocation Affichage |
- ----------------------------------------------------------------------------*/
/*
Desallocation de l'affichage : Cette fonction libère l'espace alloué par
DirectDraw. Il faut vérifier que ces interface aient été allouées d'où
l'importance d'initialiser les pointeurs vers elle à NULL. Lorsque les
interfaces sont initialisée, le pointeur prend donc leur adresse, et on sait
donc que l'on peut les désallouer. Voici donc l'interet de verifier la
non-nullité des pointeurs vers les interfaces avant d'appeler Release()
void DesallocationAffichage()
{
free(AlignY);
if (SurfaceTexte)
SurfaceTexte->Release();
if (SurfaceSecondaire)
SurfaceSecondaire->Release();
if (SurfacePrimaire)
SurfacePrimaire->Release();
if (InterfaceDirectDraw)
InterfaceDirectDraw->Release();
}
/*----------------------------------------------------------------------------*
| Affichage de texte (Fonction GDI) |
- ----------------------------------------------------------------------------*/
/*
Cette fonction fait appel a une fonction GDI de Windows pour afficher du
texte, cela grace a la compatibilité de DirectX, qui fournit un handle context
(hdc) pour permetre a la fonction TextOut d'ecrire sur cette surface. Ici on ne
voit pas très bien le fonctionement interne de l'affichage de texte. Il est en
réalité plus compliqué puisque DirectX doit informer la fonction GDI de la
taille de la surface, de son format de pixels, etc. Ca n'a ici aucune
importance. Cette fonction est lente, mais l'affichage de texte ne recquiert pas
une grande rapidité. J'ai donc préféré la simplicité a la rapidité. (J'espère
que vous ne m'en voulez pas trop :) )
void AfficherTexte(LPDIRECTDRAWSURFACE7 Surface, char* Text, int X, int Y, bool Center, DWORD TextColor, DWORD BackColor)
{
if (InterfaceDirectDraw) //La fonction s'eecute que si l'affichage est initialisé
{
HDC hdc;
if (Surface->GetDC(&hdc) == DD_OK) //Creer un contexte de dessin et bloquer la surface pour cet usage
{
if (Center)
SetTextAlign(hdc, TA_CENTER); //Centrer le texte sur (x,y) si Center est spécifié
else
SetTextAlign(hdc, TA_LEFT | TA_TOP); //Sinon (x,y) corespond au coin haut droit du cadre de texte
SetTextColor(hdc,TextColor); //Selection de la couleur du texte
SetBkColor(hdc,BackColor); //Et du fond
TextOut(hdc, X, Y, Text, strlen(Text)); //Ecrire le texte
Surface->ReleaseDC(hdc); //Débloquer la surface pour qu'elle puisse etre réutilisée normalement
}
}
}
/*----------------------------------------------------------------------------*
| Effacer une surface |
- ----------------------------------------------------------------------------*/
/*
Cette fonction efface une surface quatre octets par quatre octets. Elle est
optimisée
void EffacerSurface(LPDIRECTDRAWSURFACE7 Surface)
{
if (InterfaceDirectDraw) //La fonction s'eecute que si l'affichage est initialisé
{
DDSURFACEDESC2 ddsd;
ddsd.dwSize = sizeof(ddsd);
if (Surface->Lock(NULL, &ddsd, DDLOCK_WAIT, NULL) == DD_OK) //Bloquer la surface pour l'ecriture
{
register int* FinDeSurface = (int*)((char*)ddsd.lpSurface + ddsd.dwHeight * ddsd.lPitch); //Fin de la surface (lPitch est le nombre d'octets entre deux lignes)
for (register int* x = (int*)ddsd.lpSurface ; x < FinDeSurface ; x++)
Surface->Unlock(NULL); //Debloquer la surface pour usage posterieur
}
}
}
/*----------------------------------------------------------------------------*
| Dessiner les Flammes |
- ----------------------------------------------------------------------------*/
/*
Voici la fonction interessante : Elle va dessiner les flammes. J'ai tout
d'abord défini des macros pour obtenir la valeur d'un pixel et une pour changer
la valeur d'un pixel. Elle sont optimisés pour le mode d'affichage en couleur
32 bits , mais ne vérifient jamais si le pixel est hors ecran, ce qui est
dangereux si on controle mal son usage.
Ensuite viens la fonction proprement dite. Elle suit l'algorythme suivant :
On trace d'abord une ligne de pixels aléatoirement (appelée foyer). Dans cet
exemple elle n'est pas réelement aléatoire. J'ai choisi de faire suivre aux
couleurs une fonction sinusoidale irégulière. C'est a dire que l'angle
n'évolue pas uniformenent. Cela donerait si on voulait tracer la courbe
représentative de la fonction, des formes courbes mais sans direction apparente
ni periode.
L'étape suivante consiste a fiare partir les flammes du foyer. Ici j'ai
choisit d'attribuer a chaque pixel la valeur moyenne des pixels inferieurs (bas
gauche, bas milieu, bas droit). Cela permet d'avoir une continuité dans les
flammes. J'ai mis un plus fort coeficient devant le pixel juste en dessous, de
telle sorte que les flammes soient plus fines. J'ai ajouté a cette moyenne la
couleur du pixel précédent a la même place. Cela donne encore une continuité
mais cette fois-ci dans le temps.
L'effet d'ondulation est effectué par un décalage (quie j'ai appelé z) de
chaque ligne suivant la fonction sinus. Pour que L'ondulation ne soit pas
régulière, j'ai décalé l'angle de départ de la fonction sinus a chaque affichage
d'une valeur aléatoire (c).
Il faut que la pointe des flammes soit plus faible. Avant l'affichage de
chaque pixel, on décrémente donc sa valeur de 1. De cette manière le foyer est
nécessairement le plus lumineux.
Il y a bien sur d'autre algorythmes, et d'autre effet pour dessiner des
flammes.
#define GetLastPixel(X, Y) (*((int*)ddsd1.lpSurface + AlignY[(Y)] + (X))) //Obtention de la couleur du pixel sur la Surface Primaire (Couleur précédente)
#define GetCurrentPixel(X, Y) (*((int*)ddsd2.lpSurface + AlignY[(Y)] + (X))) //Obtention de la couleur du pixel sur la Surface Secondaire (Couleur courante)
#define SetPixel(X, Y, Couleur) (*((int*)ddsd2.lpSurface + AlignY[(Y)] + (X)) = (int)(Couleur)) //Définition de la couleur d'un pixel sur la surface secondaire
void DessinerFlammes()
{
if (InterfaceDirectDraw)
{
DDSURFACEDESC2 ddsd1, ddsd2;
ddsd1.dwSize = ddsd2.dwSize = sizeof(DDSURFACEDESC2);
//Blocage des surfaces pour pouvoir y dessiner
if (SurfacePrimaire->Lock(NULL, &ddsd1, DDLOCK_WAIT, NULL) == DD_OK &&
SurfaceSecondaire->Lock(NULL, &ddsd2, DDLOCK_WAIT, NULL) == DD_OK)
{
float c;
//Tracé du foyer
for (int x = 10 ; x < LargeurEcran-12 ; x++, c += (float(random(100)-50)/140)) //Changer 140 par une valeur plus grande pour avoir des flammes au foyer plus fin
SetPixel(x, 180, (sin(c)+1)/2*(ddsd1.ddpfPixelFormat.dwBBitMask)); //ddsd1.ddpfPixelFormat.dwBBitMask est la couleur bleue. Par ailleur : 0 < sin(c)+1)/2 < 1
c += random(100); //Décalage d'angle pour la fonction sinus
for (register int y = 179 ; y > 60 ; y--) //Tracé du corps de la flamme
{
int z = sin((float)y/5+c)*2; //Décalage de chaque ligne pour l'ondulation
for (register int x = 1 ; x < LargeurEcran-2 ; x++) //Tracé de la ligne de flamme
{
int NouvelleCouleur;
//Calcul de la couleur moyenne
if (NouvelleCouleur = (GetCurrentPixel(x-1+z, y+1) + GetCurrentPixel(x+z, y+1)*16 + GetCurrentPixel(x+1+z, y+1) + GetLastPixel(x,y)*2)/20)
NouvelleCouleur--; //Attenuation de la flamme
SetPixel(x, y, NouvelleCouleur); //Tracé du pixel
}
}
}
SurfacePrimaire->Unlock(NULL);
SurfaceSecondaire->Unlock(NULL);
}
}
//----------------------------------------------------------------------------
/*----------------------------------------------------------------------------*
| |
| Dessiner des Flammes, Unité d'affichage V.1.0 (Affichage.h) |
| |
| programmé par Blustuff |
| |
- ----------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------*
| Includes |
- ----------------------------------------------------------------------------*/
#include <ddraw.h> //Pour l'interface IDirectDraw
#include <stdlib.h> //Pour random()
#include <math.h> //Pour sin()
/*----------------------------------------------------------------------------*
| Propriétés de l'Affichage |
- ----------------------------------------------------------------------------*/
/*
Les valeurs suivantes doivent corespondre a un mode d'ecran existant et supporté
sinon l'initialisation ne fonctionera pas
#define LargeurEcran 320
#define HauteurEcran 200
/*----------------------------------------------------------------------------*
| Variables Globales |
- ----------------------------------------------------------------------------*/
LPDIRECTDRAW7 InterfaceDirectDraw = NULL;
LPDIRECTDRAWSURFACE7 SurfacePrimaire = NULL; //Primary Buffer. En clair, interface représentant la mémore vidéo directement reliée à l'ecran
LPDIRECTDRAWSURFACE7 SurfaceSecondaire = NULL; //Back Buffer. Surface sur laquelle on dessine avant de proceder au bliting (Je n'epliquerai pas le bliting ici)
LPDIRECTDRAWSURFACE7 SurfaceTexte = NULL; //Surface pour afficher le texte
int* AlignY; //Tableau de valeurs indiquant la taille en octet jusqu'a une ligne de l'ecran
/*----------------------------------------------------------------------------*
| Fonctions |
- ----------------------------------------------------------------------------*/
bool InitialiseAffichage(HWND hWnd);
void DesallocationAffichage();
void AfficherTexte(LPDIRECTDRAWSURFACE7 Surface, char* Text, int X, int Y, bool Center = true, DWORD TextColor = 0x00ffffff, DWORD BackColor = 0x00000000);
void EffacerSurface(LPDIRECTDRAWSURFACE7 Surface);
void DessinerFlammes();
//----------------------------------------------------------------------------
Conclusion :
Peut ramer sur certains PC mais pas sur mon 200 MHz (Je suis ébloui devant certeines choses inexpliquables)
Vous n'êtes pas encore membre ?
inscrivez-vous, c'est gratuit et ça prend moins d'une minute !
Les membres obtiennent plus de réponses que les utilisateurs anonymes.
Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.
Le fait d'être membre vous permet d'avoir des options supplémentaires.