• Create Account

\$89

Like
0Likes
Dislike

# Fire

By Shaun Patterson | Published Oct 19 1999 12:36 PM in Graphics Programming and Theory

tap fire fire_buffer pixel pixels line array char loop
 If you find this article contains errors or problems rendering it unreadable (missing images or files, mangled code, improper text formatting, etc) please contact the editor so corrections can be made. Thank you for helping us improve this resource

Fire is probably one of my most favorite effects. It can be very rewarding when you program a very efficient fire routine.

In this tutorial, I will show you how to make a nice fire effect in DirectX 5.0. Now, this probably can be converted into any other language if you work hard enough =)

First of all, in this tutorial, I'm not going to explain how to set up the DirectDraw interface or any of that other fun stuff. I suspect you can find this at Sweet.Oblivion or GPMega. I will, however, be showing you how to set up a palette, do a simple fire loop, and then draw the fire. Fire is really a simple effect. Kinda fun to program too =)

Unfortunately I do not know Assembly, so this isn't the FASTEST fire routine in the world..but it does run incredibly fast on my p2 (duh =) and not too bad on my p75.
Ok. Down to business. First of you need to set up a palette. Anything really would work. I did mine this way:

Black at the bottom(this will be at the top of the flame), red, an orangish color, and then yellow.

In order to set up off of our 256 colors, I just used a few loops. Now we need "containers" heh.. to hold our palette info.

LPDIRECTDRAWPALETTE lpDDPal;  	// the palette object
PALETTEENTRY 	mypal[256];  	// stores palette stuff

That's pretty explanatory I guess. Ok now we need to load our info. First we load our RGB info into each of our "mypal" array items. (Why I used a loop..)

index = 0;

for (index=95;index<200;index++)
{
mypal[index].peRed   = index+70;
mypal[index].peGreen = index+30;
mypal[index].peBlue  = rand()%10;
}
for (index = 1; index < 35; index++)
{
mypal[index].peRed   = index+25;
mypal[index].peGreen = rand()%10;
mypal[index].peBlue  = rand()%10;
}
for (index = 35; index < 55; index++)
{
mypal[index].peRed   = index+25;
mypal[index].peGreen = index-25;
mypal[index].peBlue  = rand()%10;
}
for (index = 55; index < 95; index++)
{
mypal[index].peRed = index+75;
mypal[index].peGreen = index;
mypal[index].peBlue = rand()%5;
}
for(index = 200; index < 255; index++)
{
mypal[index].peRed = index;
mypal[index].peGreen = index-rand()%25;
mypal[index].peBlue = rand()%5;
}

This is what I found to be the best combination, although I didn't spend more than 10 minutes on it =)

All it basically does is load the RGB value into each of the items. peRed member of the mypal struct is the red color value, peGreen being the green, and so on

Now we sorta attach those values to our palette object (lpDDPal) and attach those to the primary surface. So then the program uses our palette we just made

lpDD->CreatePalette(DDPCAPS_8BIT | DDPCAPS_ALLOW256, mypal, &lpDDPal, NULL);
lpDDSPrimary->SetPalette(lpDDPal);

lpDD = our directdraw object
lpDDSPrimary = hmm let me guess... uh our primary surface?

With me so far? No? Hmm, not good. Go read another tutorial then! Geez! People these days...

Still holding your breath? Well breathe. The rest is a breeze.
dun dun dun dunnnn

Fire Calculations!

First me make this little function to lock the back surface so we can draw to it:

unsigned char *Lock_Back_Buffer (void)
{
DDSURFACEDESC ddsd;
HRESULT ret;
ddsd.dwSize = sizeof(ddsd);

ret = DDERR_WASSTILLDRAWING;

while (ret == DDERR_WASSTILLDRAWING)
ret = lpDDSBack->Lock(NULL, &ddsd, 0, NULL);

return (ret == DD_OK ? (unsigned char *)ddsd.lpSurface : NULL);
}

now this returns an unsigned char pointer to the screen array. So we need to get a screen array!

unsigned char *double_buffer = NULL;

now to allocate some memory for it

double_buffer = (unsigned char *)malloc(307200);

Oh yeah. I'm working in 640x480x8bit sorry.

Now. We need another unsigned char pointer array to store our fire stuff

unsigned char *fire_buffer = NULL;
fire_buffer = (unsigned char *)malloc(307200);

now don't forget to free those when you exit -

(double_buffer); free(fire_buffer);

Ok.. whew. Now to do fire, you plot some random pixels at the bottom of the screen - the bottom line =)

You can color those pixels either 0 or 255. We do this by:

for(x = 1; x < 637; x+=rand()%3)
{
if(rand()%2)
fire_buffer[(480*640) + x] = 255;
else
fire_buffer[(480*640) + x] = 0;
}

This is how I like to do it. You can also do it without the first random.

Ok the way we do fire is, you take the surrounding pixels of a pixel and divide by the number of pixels. Now you may be thinking...WHAT?! Well it's simple. You have a point right? Well if you are familiar with plotting pixels you know you do Y position * ScreenWidth + x position to find the pixel position in a 1d array. Confusing? Good. This means you'll have to think =)

Think of it as a typewriter.

tap tap tap tap tap tap CA CHING - next line
tap tap tap tap tap tap CA CHING - next line
tap tap tap tap tap tap CA CHING - next line

etc...

In this example, the screen is only 6 taps long. Well. Say you wanted to plot a pixel at tap on the 2nd line and the 3rd column. Well 2*6 is what? 12. 12 + 3 is what? 15? whoa! quick one here.

now let's count 1, 2, 3...15. Wait a minute. We are on the 3 LINE! Start counting from 0 then 1 2 3 ok?

so 1*6 is 6. Then 6 + 3 is 9. Now we're into business

::sigh:: What next? Ahh yes. Our loop. We need to loop through the section of the screen we are painting the fire at. Now in that loop, we must loop through both the Y and then the X. You'll see why in a minute. In the inner X loop, as I said before, we average the surrounding pixels to get our new color value. Remember our palette. Well, if our pixel that we randomized was 255, it's yellow. If it was 0, it's black. Now 255+0 is what? 255. 255/2 is..? About 128. Whoa! We are in our orange redish section of our palette. This is basically all fire is. Averaging colors so you can get a new new color as the fire moves up the line. The more you think about it, the more clear it will become. It will start yellow, go to orange, red, and then finally disappear as it gets to very faint red and then black. As it moves up the fire line, it will make cool fire shapes. You'll see after you see the example project.

pseudo code:

for y = 1, y to screenheight, increment y
for x = 1, x to screenwidth, increment x
find our offset - what pixel we are going to start
averaging around - (Y*ScreenHeight)+x
add up the surrounding pixels - all eight of them
divide that total by 8 - hard concept there =)

now if that value is not 0 - not black
we decrement it - subtract 1 =)

end

Draw to our back buffer

end

Ok. C++ code

int x,y,fireoffset;

//calculate the bottom line pixels
for(x = 1; x < 637; x+=rand()%3)
{
if(rand()%2)
fire_buffer[(480*640) + X] = 255;
else
fire_buffer[(480*640) + X] = 0;
}
}

//CALCULATE THE SURROUNDING PIXELS
for(y = 1; y < 480; ++y)
{
for(x = 1; x < 640; ++x)
{
offset = (y*640) + x;
firevalue = ((fire_buffer[fireoffset-640] +
fire_buffer[fireoffset+640] +
fire_buffer[fireoffset+1] +
fire_buffer[fireoffset-1] +
fire_buffer[fireoffset-641] +
fire_buffer[fireoffset-639] +
fire_buffer[fireoffset+641] +
fire_buffer[fireoffset+639]) / 8); // this can be optimized by a
// look up table as I'll show you later

if(firevalue != 0) // is it black?
{
--firevalue;   	// Nope. Beam me down Scotty.
fire_buffer[fireoffset-640] = firevalue;	// Plot that new color
// on the above pixel
// remember the typewriter analogy
}
}
}

double_buffer = Lock_Back_Buffer();   // Remember this function?  Good
memcpy(double_buffer, fire_buffer, (640*480));  // Copy fire buffer to the screen
lpddsback->Unlock(NULL);         	// Unlock! Important! you have no idear!

ok this may seem overwhelming at first, but really, it's simple if you think about it.

We have this pixel we found. As I explained above - Y * 640 + X gave us our fireoffset - the pixel we are averaging around

Now, fireoffset-640 would be what? The pixel above it right? fireoffset-639 is what? A pixel above..but a little to the right. So that's the topright corner pixel. fireoffset+640 is what? The pixel below. Get the drift?

I told you this was SIMPLE! Ha! And you didn't believe me. I think it may be confusing at first.. but after you think about it for a while, it will finally click - like it did for me

Ok this is a nice effect.. but it's kinda slow. So we optimize.

We could take out that / 8 and put in a right shift ( divide by powers of 2) >> 3; 2^2^2 = 8. So a left shift multiplies and a right shift divides. BUT! Why even multiply?

We could make a nifty little look up table. Like an array that we just have to look up a value, and it automatically gives you the value we want - because it's pre-calculated.

so...

int ftab[1257]; // I'm not sure how many members to put into this array..
// 1257 seems to work good =)

now to build that table

int firelook;
for (firelook = 0; firelook < 1256; FIRELOOK++)
FTAB[FIRELOOK] = FIRER >> 3;

voila! Now we have our division pre-calculated. Simple. Now to incorporated it into our code:

firevalue = ftab[(fire_buffer[fireoffset-640] +
fire_buffer[fireoffset+640] +
fire_buffer[fireoffset+1] +
fire_buffer[fireoffset-1] +
fire_buffer[fireoffset-641] +
fire_buffer[fireoffset-639] +
fire_buffer[fireoffset+641] +
fire_buffer[fireoffset+639])];

See, the operation is completely removed. We just look up the value in our little array.
Hopefully this little weekend excursion will help you make your own fire effect and get you started making all sorts of neet effects.

Any questions? Tough =)

Shaun Patterson