Blue fire (animation de flammes - exemple directdraw plein ecran)

Description

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) {
  • hWnd = 0;
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++)
  • x = 0;
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)

Codes Sources

A voir également

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.