This demo uses some simple physics to simulate an explosion with a particle system. It looks pretty awesome considering the small amount of code used for the particle system. You can press the spacebar to keep making new explosions which makes this a fun demo to play with.
One method - BlurScreen - is wrapped by much faster standard assembly code. For directx window 320x200 (also original size) - this wrapper is not so important.
This example uses 8 bit color depth and custom color palette: while->yellow->red
note: instruction cmovc can be unsupported on older processors, here is replacement (replace cmovc eax,ecx with ...):
jnc .nocy
mov eax,ecx
.nocy:
///////////////////////////////////////////////////////////
//ÂÃ, Particle Explosion in DirectX 5
//ÂÃ, based on Tom Hammersley's particle code for DOS
///////////////////////////////////////////////////////////
import int ZeroMemory alias RtlZeroMemory(void *Destination,int Length);
import int MoveMemory alias RtlMoveMemory(void *Destination,void *Source,int Length);
#define PC_NOCOLLAPSEÂÃ, Ã‚Ã, 0x04
// D E F I N E S //////////////////////////////////////////
// change partition direction if partition reaches left, right or top border
//#define BORDER_COLLISIONS
// Defines for video mode
#define SCREEN_WIDTHÂÃ, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, 800//320ÂÃ, Ã‚Ã, Ã‚Ã, // size of screen
#define SCREEN_HEIGHTÂÃ, Ã‚Ã, Ã‚Ã, Ã‚Ã, 600//200
#define SCREEN_BPPÂÃ, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, 8ÂÃ, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, // update code if changed! (8bpp=UCHAR)
#define SCREEN_SIZEÂÃ, Ã‚Ã, Ã‚Ã, Ã‚Ã, (SCREEN_WIDTH * SCREEN_HEIGHT)
// divide 10000 particles to 4 banks
#define MAX_PARTICLESÂÃ, Ã‚Ã, Ã‚Ã, 20000
#define PARTICLE_BANKSÂÃ, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, 8
#define PARTICLE_BANK_SIZE (MAX_PARTICLES/PARTICLE_BANKS)
// S T R U C T U R E S /////////////////////////////////////
struct PARTICLE_STRUCT
{
float x;ÂÃ, Ã‚Ã, Ã‚Ã, // horizontal position
float y;ÂÃ, Ã‚Ã, Ã‚Ã, // vertical position
float dirx;ÂÃ, // horizontal move direction
float diry;ÂÃ, // vertical move direction
intÂÃ, Ã‚Ã, color;
intÂÃ, Ã‚Ã, life;ÂÃ, // life time
}
#typedefÂÃ, UCHAR unsigned byte
struct PALETTEENTRY // from wingdi.inc
{
ÂÃ, Ã‚Ã, unsigned byte peRed;
ÂÃ, Ã‚Ã, unsigned byte peGreen;
ÂÃ, Ã‚Ã, unsigned byte peBlue;
ÂÃ, Ã‚Ã, unsigned byte peFlags;
}
class CFireworks : C2DScreen
{
C2DSurfaceÂÃ, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, *m_back ;ÂÃ, // dd primary surface
PALETTEENTRYÂÃ, Ã‚Ã, Ã‚Ã, Ã‚Ã, m_palette[256];ÂÃ, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, // color m_palette
UCHARÂÃ, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, *m_VideoBuffer ; // primary video buffer
UCHARÂÃ, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, *m_MemoBackBuffer; // double buffer (system RAM)
CDirectInputÂÃ, Ã‚Ã, Ã‚Ã, Ã‚Ã, m_keyboard;
intÂÃ, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, m_ytable[SCREEN_HEIGHT], i;
PARTICLE_STRUCTÂÃ, Ã‚Ã, *m_particles;
floatÂÃ, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, m_gravity;
floatÂÃ, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, m_energy;
intÂÃ, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, m_particlescnt;
intÂÃ, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, m_particleLife;
intÂÃ, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, m_particleLifeLimit; // if reached: particle.color--
intÂÃ, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, Ã‚Ã, m_particlesBank;ÂÃ, Ã‚Ã, Ã‚Ã, // bank index for new explosion RandomExplosion()
declare CFireworks()
{
m_gravity = 0.07f;
m_energyÂÃ, = 10.0f;
m_particlescnt = PARTICLE_BANK_SIZE; // MAX_PARTICLES = PARTICLE_BANK_SIZE * PARTICLE_BANKS
m_particleLife = 150; // depends on FPS
m_particleLifeLimit = 140;
m_particlesBank = 0;
m_MemoBackBuffer = null; // one memory block:ÂÃ, Ã‚Ã, m_MemoBackBuffer | m_particles
}
declare _CFireworks()
{
if m_MemoBackBuffer delete m_MemoBackBuffer;
}
declare Init(),int;
declare Shutdown();
declare Main(),int;
declare CreateFirePalette();
declare Explosion(int x, int y);
declare RandomExplosion();
declare DrawParticle(PARTICLE_STRUCT *aparticle);
declare DrawParticles();
declare MoveParticles();
declare BlurScreen();
declare WaitForVsync();
declare ShowDoubleBuffer();
declare virtual OnTimer(int nIDEvent),int
{
StopTimer(1);
RandomExplosion();
StartTimer(rand(200, 2000)); // average explosion frequency
}
}
///////////////////////////////////////////////////////////
// W I N M A I N //////////////////////////////////////////
///////////////////////////////////////////////////////////
global sub main()
{
CFireworks *pf = new(CFireworks, 1);
if (pf)
{
if pf->Init()
{
pf->StartTimer(1000);
// enter main event loop
while (!pf->m_keyboard.KeyDown(DIK_ESCAPE))
{
pf->Main();
}
}
else messagebox(0,"CFireworks::Init failed","");
pf->Shutdown();
delete pf;
}
}
///////////////////////////////////////////////////////////
// WINX GAME PROGRAMMING CONSOLE FUNCTIONS ////////////////
///////////////////////////////////////////////////////////
//
// These functions are coded DOS style!ÂÃ, Oh yeah!
//
///////////////////////////////////////////////////////////
CFireworks::Init(),int
{
int index;
// This function is where you do all the initialization for your game
// Allocate memory for m_particles and double buffer
m_MemoBackBuffer = new(byte, (MAX_PARTICLES * sizeof(PARTICLE_STRUCT)) + SCREEN_SIZE);
m_particles = m_MemoBackBuffer + SCREEN_SIZE;
if (!m_MemoBackBuffer) return false;
if CreateFullScreen(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP) < 0 return false;
SetCursor(CS_CUSTOM,0);
SetFont("Verdana", 8, 800);
// Set up DirectInput
m_keyboard.Init(this);
// Create the primary surface!
m_back = GetBack();
if (!m_back) return false;
// Create a 256 color m_palette with a good set of fire colors
CreateFirePalette();
// Do first explosion
Explosion(SCREEN_WIDTH/2.0, SCREEN_HEIGHT/2.0);
// return success
return true;
}
///////////////////////////////////////////////////////////
CFireworks::Shutdown()
{
CloseScreen();
}
///////////////////////////////////////////////////////////
// G A M EÂÃ, Ã‚Ã, M A I N /////////////////////////////////////
///////////////////////////////////////////////////////////
CFireworks::Main(),int
{
// This is the workhorse of your game
// All the calls for your main game loop go here!
m_back->Fill(0);
DrawParticles();
MoveParticles();
// Without this the explosion looks like a pixel explosion
// rather than a real fire explosion
BlurScreen();
// Tell DirectDraw we are accessing video memory
if m_back->Lock()
{
// Alias a pointer to the surface memory for easy access
m_VideoBuffer = m_back->m_DDSD.lpSurface;
// Synchronize to vertical retrace to eliminate flicker
// and lock the frame rate.
WaitForVsync();
// Copy double buffer into video memory
ShowDoubleBuffer();
BackPen(0);
DrawMode(TRANSPARENT);
FrontPen(0xFFFFFF);
WriteText(0,0,sprint("gravitation:",m_gravity," (left,right)"));
WriteText(0,14,sprint("explosion energy:",m_energy," (up,down)"));
WriteText(0,28,sprint("explosion size:",m_particlescnt*100/PARTICLE_BANK_SIZE,"% (page up,down)"));
FrontPen(0x00FFFF);
WriteText(0,42,"SPACE - fireÂÃ, Ã‚Ã, ESC - quit");
// Tell DirectDraw we are done accessing video memory
m_back->Unlock();
Flip();
if (m_keyboard.KeyDown(DIK_LEFT) && (m_gravity > 0.0)) m_gravity -= 0.0025;
if (m_keyboard.KeyDown(DIK_RIGHT)) m_gravity += 0.0025;
if (m_keyboard.KeyDown(DIK_DOWN) && (m_energy > 0.0)) m_energy -= 0.01;
if (m_keyboard.KeyDown(DIK_UP)) m_energy += 0.1;
if (m_keyboard.KeyDown(DIK_PRIOR) && (m_particlescnt < PARTICLE_BANK_SIZE)) m_particlescnt += 10;;
if (m_keyboard.KeyDown(DIK_NEXT) && (m_particlescnt)) m_particlescnt -= 10;
}
// Do another explosion if the user pressed the spacebar
if m_keyboard.KeyDown(DIK_SPACE)
RandomExplosion();
// return success
return(1);
} // end Game_Main
////////////////////////////////////////////////////////////////
// F U N C T I O N S ///////////////////////////////////////////
////////////////////////////////////////////////////////////////
CFireworks::CreateFirePalette()
{
// First clear out all the entries, defensive programming
ZeroMemory(m_palette,256*sizeof(PALETTEENTRY));
// Here, we make a nice m_palette, that fades from red->yellow->white,
// giving the usual spread of colors seen in an explosion
// Set first 64 colors to shades of red
for (int i=0; i<64; i++)
{
m_palette[i].peFlags = PC_NOCOLLAPSE;
m_palette[i].peRed = (i << 2);
m_palette[i].peGreen = 0;
m_palette[i].peBlue = 0;
}
// Set next 64 colors to shades of yellow and orange
for (i=64; i<128; i++)
{
m_palette[i].peFlags = PC_NOCOLLAPSE;
m_palette[i].peRed = 255;
m_palette[i].peGreen = (i - 64) << 2;
m_palette[i].peBlue = 0;
}
// Set last 128 colors to yellowish-white
for (i=128; i<256; i++)
{
m_palette[i].peFlags = PC_NOCOLLAPSE;
m_palette[i].peRed = 255;
m_palette[i].peGreen = 255;
m_palette[i].peBlue = (i - 128) << 1;
}
// set the new palette
IDirectDrawPalette *lpddpal;
if !m_lpDD->CreatePalette(DDPCAPS_8BIT | DDPCAPS_INITIALIZE | DDPCAPS_ALLOW256, m_palette,&lpddpal,NULL)
{
GetFront()->m_lpDDS->SetPalette(lpddpal);
lpddpal->Release();
}
// Store coordinates of the start of each row in a table
for (i=0; i<SCREEN_HEIGHT; i++)
{m_ytable[i] = i*SCREEN_WIDTH;}
}
/////////////////////////////////////////////////////////////////
CFireworks::RandomExplosion()
{
Explosion(RND(SCREEN_WIDTH-20)+10, RND(SCREEN_HEIGHT-20)+10);
}
/////////////////////////////////////////////////////////////////
CFireworks::Explosion(int x,int y)
{
intÂÃ, i;
float d_len, lx, ly, dist;
m_particlesBank++;
if (m_particlesBank >= PARTICLE_BANKS)
m_particlesBank = 0;
int startPartition = (m_particlesBank * PARTICLE_BANK_SIZE);
int lastPartitionÂÃ, = (startPartition + m_particlescnt-1);
for (i=startPartition; i<lastPartition; i++)
{
PARTICLE_STRUCT *part = *m_particles[i];
// Set coordinates for origin of explosion
if (!part->life) // renew only unused particles
{
part->x = x;
part->y = y;
part->life = rand(1, m_particleLife&32767);
// Set the x and y directions to either a positive or negative
// i.e. right/left or up/down
part->dirx = RND(3.0) - 1.5;
part->diry = RND(3.0) - 1.5;
// Set the energy (power) of the explosion
dist = RND(m_energy);
// Store x,y directions in temp variables
lx = part->dirx;
ly = part->diry;
// Use distance formula
d_len = sqrt(lx*lx + ly*ly);
if (d_len == 0.0)
d_len = 0.0
else
d_len = 1.0 / d_len;
// We need to normalize the vector to give us a direction
// vector, then find a random length of the vector, to give
// a nice, non-uniform distribution
part->dirx *= (d_len*dist);
part->diry *= (d_len*dist);
// Set PARTICLE_STRUCT to brightest color
part->color = 255;
}
ÂÃ, Ã‚Ã, Ã‚Ã, }
}
////////////////////////////////////////////////////////////////
CFireworks::DrawParticle(PARTICLE_STRUCT *aparticle)
{
if (aparticle->life)
{
aparticle->life--;
if (aparticle->color)
{
if (aparticle->life < m_particleLifeLimit) aparticle->color--;
}
else
{
aparticle->life = 0;
}
}
else
{
aparticle->color = 0;
}
// Round coordinates to integers
int x = (aparticle->x + 0.5);
int y = (aparticle->y + 0.5);
// Draw only if inside screen area
if ((x >= 0) && (x < SCREEN_WIDTH) && (y >=0) && (y < SCREEN_HEIGHT))
{*m_MemoBackBuffer[m_ytable[y] + x] = aparticle->color;}
}
/////////////////////////////////////////////////////////////////
CFireworks::DrawParticles()
{
PARTICLE_STRUCT *part = m_particles;
// Draw all particles
for (int i=0; i<MAX_PARTICLES; i++)
{
if (part->color) DrawParticle(part);
part+=sizeof(PARTICLE_STRUCT);
ÂÃ, Ã‚Ã, }
}
/////////////////////////////////////////////////////////////////
CFireworks::MoveParticles()
{
int i;
PARTICLE_STRUCT *part = m_particles;
for (i=0; i<MAX_PARTICLES; i++)
ÂÃ, Ã‚Ã, {
// Move particles according to vector direction
part->x += part->dirx;
part->y += part->diry;
// Check if the PARTICLE_STRUCT has collided with any of the screen
// top or bottom edges. If so, reflect the direction, and
// lose energy. If not, then just add m_gravity
if (part->y >= SCREEN_HEIGHT)
{
part->y = SCREEN_HEIGHT-1;
part->dirx /= 4;
part->diry = (-part->diry / 2);
}
else if (part->y < 1)
{
part->y = 1;
#ifdef BORDER_COLLISIONS
part->dirx /= 4;
part->diry = (-part->diry / 2);
#else
part->color = 0;
part->life = 0;
#endif
}
else
{
part->diry += m_gravity;
}
if (part->x < 0)
{
#ifdef BORDER_COLLISIONS
part->x = 1;
part->dirx = (-part->dirx / 2);
part->diry /= 4;
#else
part->x = 0;
part->color = 0;
part->life = 0;
#endif
}
else if (part->x >= SCREEN_WIDTH)
{
part->x = SCREEN_WIDTH-1;
#ifdef BORDER_COLLISIONS
part->dirx = (-part->dirx / 2);
part->diry /= 4;
#else
part->color = 0;
part->life = 0;
#endif
}
// If the PARTICLE_STRUCT is near the bottom of the screen, we
// make its color random, to simulate the effect of the
// PARTICLE_STRUCT dying, things catching fire, that kind of stuff
if (part->y >= (SCREEN_HEIGHT-20))
part->color = (rand(127)) + 128;
part+=sizeof(PARTICLE_STRUCT);
ÂÃ, Ã‚Ã, Ã‚Ã, }
}
/////////////////////////////////////////////////////////////////
CFireworks::BlurScreen()
{
_BlurScreen(m_MemoBackBuffer, m_ytable, SCREEN_WIDTH, SCREEN_HEIGHT);
/*
// this is very slow loop, used asm version
UCHAR *row1, *row2, *row3;
int x, y, color;
for (y=1; y<(SCREEN_HEIGHT-1); y++)
{
// Get values of pixel above and below the PARTICLE_STRUCT's row
row1 = *m_MemoBackBuffer[m_ytable[y - 1]];
row2 = *m_MemoBackBuffer[m_ytable[y]];
row3 = *m_MemoBackBuffer[m_ytable[y + 1]];
// Set value of next row's pixel to 0 to prevent sides of screen
// from staying ablaze
*row1 = 0; row1++;
*row2 = 0; row2++;
*row3 = 0; row3++;
// Take the average of the 4 pixels on each side of the current pixel
// and darken it a bit too
for (x=1; x<(SCREEN_WIDTH-1); x++)
{
color = ((*row1 + *row3 + *(UCHAR)(row2 - 1) + *(UCHAR)(row2 + 1)) >> 2) - 2;
if (color < 0)
color = 0;
// Set current pixel to calculated color
*row2 = color;
row1++;
row2++;
row3++;
}
// Set value of next row's pixel to 0 to prevent sides of screen
// from staying ablaze
*row1 = 0; row1++;
*row2 = 0; row2++;
*row3 = 0; row3++;
ÂÃ, Ã‚Ã, Ã‚Ã, }
*/
ZeroMemory(m_MemoBackBuffer,SCREEN_WIDTH);
}
/////////////////////////////////////////////////////////////////
CFireworks::WaitForVsync()
{
// this function waits for a vertical blank to begin
m_lpDD->WaitForVerticalBlank(DDWAITVB_BLOCKBEGIN,0);
}
/////////////////////////////////////////////////////////////////
CFireworks::ShowDoubleBuffer()
{
// This function copies the double buffer into the video buffer
// and takes the pitch into account for retarded video cards.
int pitch_off = 0;
int work_off = 0;
int pith = m_back->m_DDSD.lPitch;
for (int y=0; y<SCREEN_HEIGHT; y++)
{
// Copy double buffer into video memory one line at a time
MoveMemory(*m_VideoBuffer[pitch_off],*m_MemoBackBuffer[work_off],SCREEN_WIDTH);
// Move to the next line
pitch_off += pith;
work_offÂÃ, += SCREEN_WIDTH;
}
}
sub _BlurScreen(byte *m_MemoBackBuffer,int *ytable,int screen_x,int screen_y)
{
int x[6]; // make room for local variables
#asm
%define m_MemoBackBuffer [ebp+8]
%define ytable [ebp+12]
%define screen_x [ebp+16]
%define screen_y [ebp+20]
%define XÂÃ, Ã‚Ã, Ã‚Ã, [ebp-4]
%define yÂÃ, Ã‚Ã, Ã‚Ã, [ebp-8]
%define color [ebp-12]
%define row1ÂÃ, [ebp-16]
%define row2ÂÃ, [ebp-20]
%define row3ÂÃ, [ebp-24]
mov dword y,1
dec dword screen_x
;//dec dword screen_y
.for_y:
mov esi,ytable
mov edi,m_MemoBackBuffer
mov eax,y
sub eax,1
mov edx,[esi+4*eax] ;// ytable[y - 1]
lea edx,[edi+edx]ÂÃ, Ã‚Ã, ;// m_MemoBackBuffer[edx]
mov row1,edx
add eax,1
mov edx,[esi+4*eax] ;// ytable[y]
lea edx,[edi+edx]ÂÃ, Ã‚Ã, ;// m_MemoBackBuffer[edx]
mov row2,edx
add eax,1
mov edx,[esi+4*eax] ;// ytable[y + 1]
lea edx,[edi+edx]ÂÃ, Ã‚Ã, ;// m_MemoBackBuffer[edx]
mov row3,edx
mov esi,row1
mov edi,row2
mov edx,row3
mov byte[esi],0
mov byte[edi],0
mov byte[edx],0
add dword row1,1
add dword row2,1
add dword row3,1
mov dword X,1
.for_x:
movÂÃ, Ã‚Ã, esi,row2
movÂÃ, Ã‚Ã, edi,row3
movzx eax,byte[esi-1]
movzx edx,byte[esi+1]
movzx ecx,byte[edi]
addÂÃ, Ã‚Ã, eax,edx
addÂÃ, Ã‚Ã, eax,ecx
movÂÃ, Ã‚Ã, esi,row1
movzx edx,byte[esi]
addÂÃ, Ã‚Ã, eax,edx
xorÂÃ, Ã‚Ã, ecx,ecx
shrÂÃ, Ã‚Ã, eax,2
subÂÃ, Ã‚Ã, eax,2
cmovc eax,ecx
mov esi,row2
mov byte[esi],al
add dword row1,1
add dword row2,1
add dword row3,1
add dword X,1
mov eax,screen_x
cmp X,eax
jc .for_x
xor eax,eax
mov esi,row1
mov edi,row2
mov byte[esi],0
mov byte[edi],0
mov esi,row3
mov byte[esi],0
add dword row1,1
add dword row2,1
add dword row3,1
add dword y,1
mov eax,screen_y
cmp y,eax
jc .for_y
#endasm
}
I got a message error(s) in compiling.
I did try the cmovc with the change too and got the same error. I am running on a new computer.
What errors are you getting? You shouldn't have compiling errors, only runtime errors if your computer doesn't support the instruction.
I figured out somehow I messed up my aurora files. I got an error on code that worked before, so I am sure that is the case. I am going to reinstall aurora and will report back.
Ok that was the problem. THat is my Aurora got messed up. I reinstalled and it is all fine now.
The program however, does cause my system to crash when it runs. I tried both the program both ways. Has anyone been able to run the example?
tested on two machines without any problems and exceptions (celeron 4 and pentium M centrino)