Fire
by Shaun Patterson

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 explainitory 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 familar 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


Get the zipped source for this article here.

Discuss this article in the forums


Date this article was posted to GameDev.net: 10/19/1999
(Note that this date does not necessarily correspond to the date the article was written)

See Also:
Fire

© 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
Comments? Questions? Feedback? Click here!