Fire How To
by Vaelek

Header Files and Misc Info You Should Know

In order to get anywhere with this, you will need to include these header files:

#include <conio.h>
#include <stdlib.h>
#include <dos.h>
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <time.h>
#include <dos.h>
#include <alloc.h>
#include <mem.h>

Also, if you don't compile it in either LARGE or HUGE memory model, you _WILL_ get an error 'Not enough available memory, exiting...' So make _SURE_ to do this.

What You NEED To Know First

Okay, so you've got this txt file, and you want to start coding your own fire routine. What do you need to know first? Well, you will need to know how to set up a video buffer (ie, virtual screen) and flip it to the active video screen, and that's about it aside from setting the video mode, but for those of you that need it, I'll even explain how to do that as well.

Setting The Video Mode

The mode you will most likely be working in is mode 13h, which is a resolution of 320x200 pixels. To set the video card into this mode, you need to put the value 0x13 (hence 13h) into register AX on the cpu, to do this, you:

void graph_mode(void)
{
    asm
    {
        mov AX, 0x13 // move 13h into AX
        int 10h // call interrupt 10h, the video interrupt.
    }
}

When you are finished and wish to return to normal 80x25 text mode, you will repeat that function, using 0x03 this time instead of 0x13.

void text_mode(void)  
{  
    asm
    {  
        mov AX, 0x03  
        int 10h  
    }
}

Video Buffer (Virtual Screen)

Ok, now you can set your video mode, now you need to set up your video buffer. Why do you need a buffer? Because directly accessing your video memory is just simply _TOO_ damn slow for what your going to be doing.

first set up your pointer (I would make this global)

unsigned char *vbuffer = NULL;

Then, simply make a function to create your buffer:

void mk_buff(void)  
{
    vbuffer = (unsigned char *) malloc(64000);
    if (vbuffer == NULL)
    {
        text_mode();
        printf("Not enough available memory, exiting...");
        exit(1);
    }
}

Notice the size, 64k. why that size? well, in mode 13h, with screen dimensions of 320x200, if you do the math, 320 * 200 = 64000!

Pointing to Video Memory

If you don't know anything else, you should know this, you will NEED a pointer to your video memory, or _NOTHING_ is going to work. To set this up add a line like this to the beginning of your source file (make it global)

unsigned char *video = (unsigned char *)MK_FP(0xA000, 0);

Choosing a Palette

If you haven't noticed before, by some act of the gods, the default color palette isn't set up with smooth running colors, it just jumps all over the damned place.... Soooooo, you will need to set this up as well. Now don't panic, it's not that hard. If I can do it, you can do it (trust me on this one).

The first thing you need to do, set a few define's, and create your RGB tripple color structure, you will do that like this:

#define palette_mask  0x3c6  // tells the vga card we'll be messing w/ the palette
#define palette_write 0x3c8  // tells it we'll be writing values
#define palette_data  0x3c9  // this is the port we'll write to

typedef struct RGB_Type
{
    unsigned char red;
    unsigned char green;
    unsigned char blue;
} RGB_color, *RGB_color_ptr;

You've got your structure set up, now what do you do with it? Well, you'll probably want your palette to consist of shades of red (or not, it's up to you). But we'll pretend you're using red.

first make a procedure set_pal():

void set_pal(int index, RGB_color_ptr color)
{
    outp(palette_mask, 0xff);     // tell it we'll be messing w/ it
    outp(palette_write, index);   // which color we'll be updating
    outp(palette_data, color->red);
    outp(palette_data, color->green);
    outp(palette_data, color->blue);
}

OK, you've got your procedure to change the palette, now let's make one that will send the values to it:

void init_colors(void) // set up smooth running color palette
{
    int i, count;
    RGB_color_ptr col;

    for (i = 1; i < 63; i++)
    {
        col->red = i;   // there are 63 possible intensities for each color
        col->green = 0; // so we'll loop through and set from 1 to 62 for
        col->blue = 0;  // shades of red.
        set_pal(i, col);
    }

    col->red = 0;
    col->green = 0;
    col->blue = 0;
    for (i = 63; i < 255; i++) // this will set _ALL_ other colors to black
        set_pal(i, col);   // I'll explain why we do this later
}

Whew! And there you go, you've got a palette of smooth running shades of red. So what, what good does a palette do you? Hey, give me a break, i'm getting there OK! geez.

Cleaning Your Memory

Since we'll be reading values from our newly created video buffer, it'd be nice to know that when we start out, they are all zero, or we're going to have _PROBLEMS_, because un-initialized variables tend to take on ugly forms and will throw out random 'stuff' at you and bad things will happen. Sooooo, let's write a little tid-bit to do just that:

void Cls(unsigned char Col, unsigned char *Where)
{
    // initialize screen or buffer to color Col
    _fmemset(Where, Col, 64000);
}

What exactly does this bit of code do? well, the '*Where' is so you can specify _ANY_ screen you want to clean, video memory, buffer, whatever. _IMMEDIATLY_ after you create your video buffer by calling mk_buff();

Cls(0, vbuffer);

That's it! your video buffer is now primed with 0's and ready to be cluttered with all kinds of _NICE_ numbers by you..(I also do a Cls(0, video); to clear my video memory as well, but that's up to you because you will soon be writing over what's there anyways, but it's good practice).

Side note, this actually fills the screen with color 'Col' Sooo, if you use say, Cls(20, vbuffer); it will "clear" the screen to whatever color 20 happens to be set to.

A Little Optimization Tip

In this section I will describe a method of accessing the video memory or buffer faster than you normally would think you could. Most of you are probably used to using the video[Y * 320 + X] method, well, what i'm going to show you is in sense, the same thing, accept it's impregnated, that is, precalculated Y values. To do this, create a global variable

int ytab[319];

Now, you will need a function to 'stuff' this variable with your values.

void mk_ytab(void)  // create Y * 320 lookup table
{
    int x,y;
    for (y = 0; y < 319; y++)
        ytab[y] = y * 320;
}

And there you have it, this little baby will now allow you to use your coordinates like this: video[ytab[Y] + X]; saving time by not having to calculate the Y values on _EVERY_SINGLE_CALL.

Setting Up Your 'Hot Spots'

What are Hot Spots anyways? Well, they are the pixels you will set randomly on the bottom row (119) that are going to be the base of your entire routine. Without these babies, you 'aint gettin' no where... But before we get to the code for the hotspots, there is a function that you will want to add that will clear row 119 in between each run. We'll call this function cll.

void cll(unsigned char *where) //clear line 119 to black (resetting hotspots)
{
    _fmemset(where + 63680, 0, 320);
}

Ok, what is this line doing? well, the 'where' as you know is either going to be your video buffer or video memory itself. the addition of 63680 is just 119 * 320. So we're starting at row 119, and setting color 0 (black) for 320 pixels, or to the end of the line.

Ok, now back to the originally scheduled program... Hotspots. The idea here is to go through a for loop, running from 119,0 to 119,319. The reason we don't go to 320, is that it only goes to 319, but you have to remember that it starts at 0, so in essence, it's still 320 columns wide. Ok, I think you're ready to see some example code to to this, well here you go:

void set_hot(void)
{
    int x;
    cll(vbuffer); // clear line for next run
		
    for (x = 0; x < 319; x++)
    {
        if (random(100) < 55) vbuffer[ytab[199] + x] = 63;
    }
}

The _55_ is the percent chance that the pixel at 199,X will be lit. You can mess around with this value to get different effects. Basically, the higher the number is, the more intense your fire will be (and taller also)

Doing The *UGH* Math

Ok, now it's time to get our hands dirty. There will be a few functions covered / created in this section, because they are tied together. Just to keep your mind thinking in the right order, i'll show them to you in the order they are executed, however, there will be a call in this first function that you have not seen yet. But it will be defined directly after. This first function will examine _every_ single pixel on our working screen, do the calculations, and make the changes that will set you on your way.

void mk_lines(void)
{
    int x, y;

    for (x = 0; x < 319; x++)
        for (y = 199; y > 110; y--)
            vbuffer[ytab[y-1] + x] = average(x,y);
}

The value '110' is how far _UP_ from the bottom we are going to even bother with calculating. If the flames only go up to the 115th row, then why even calculate the ones above that? To do so would just be a waste of precious clock cycles, and you need them. However, if you find that the tops of your flames are getting cut off, you will either need to decrease the percentage that the hotspots are lit, or _DECREASE_ the value below 110 to make it handle positions higher up the screen.

What about that average(x,y) call you say? Well, that's the call I was refering to before. You will need a function that will take the pixels 2 to the left, 2 to the right, 1 above, 1 below, and the current one, and average them all together, Then plot your new value above the current pixel you are calculating for. Here's a diagram of how this works if you're confused:

00*0*00
02463240
00*0*00

For simplicity, we'll use a 5x3 screen here. If we are on 3,2 (the one with 63 in it) we would do the following:

foo = (int)(0 + 24 + 63 + 24 + 0 + 0 + 0) / 7;

and then we would plot the result of 16 to 3,1 (the one with the *'s) Ok, are you following me so far? I hope so, because if not, you should re-read this section until you've got it, or perhaps the code will help you out if you are that type of person (and i know _I_ am not :)

So, without further ado, here is an example average() function:

unsigned char average(int x, int y)
{
    unsigned char ave_color;
    unsigned char ave1, ave2, ave3, ave4, ave5, ave6, ave7;

    ave1 = vbuffer[ytab[y + 1] + x];
    ave2 = vbuffer[ytab[y - 1] + x];
    ave3 = vbuffer[ytab[y] + x + 1];
    ave4 = vbuffer[ytab[y] + x - 1];
    ave5 = vbuffer[ytab[y] + x + 2];
    ave6 = vbuffer[ytab[y] + x - 2];
    ave7 = vbuffer[ytab[y] + x];

    ave_color = (ave1 + ave2 + ave3 + ave4 + ave5 + ave6 + ave7) / 7;

    return(ave_color);
}

Are you seeing what's going on yet?

In the Home Stretch

Ok, we've got our hotspots set up, and we've got our averaging going, but nothing is happening. Why? Because you are drawing on the virtual screen, or video buffer. In order to see your new creation, you must transfer the virtual screen to the actual video screen. This is a simple one line function that looks like this:

void flip(void)
{
    // move contents of vbuffer to screen
    _fmemcpy(video, vbuffer, 64000);
}

BANG! Your first frame has been drawn..

One Final Task

Unless you like the idea of losing your memory to dead tasks, before your routine exits, it would be in your best interest to release your allocated video buffer back to the system. This, like flip(); is another quick and painless routine, even less typing that flip!! woopie.

void shutdown(void)
{
    free(vbuffer);
}

Whew! that one was hard eh? :)

Putting It All Into Action

So now you've got all the code (hopefully) you need to impliment your very first fire routine. All you need is a main() to go with your functions. A quick and dirty main would look something like this:

void main(void)
{
    graph_mode();
    mk_ytab();
    mk_buff();
    Cls(0, vbuffer);
    Cls(0, video);

    init_colors();
    randomize();  // this is crucial!

    do
    {
        set_hot();   // generate random hotspots
        mk_lines();  // do all the math and screen updating
        flip();      // move your new frame to the video screen
    } while(!kbhit());

    shutdown();
    text_mode();
}

C O N G R A T U L A T I O N S!!!! You now have finished your very own fire routine.

Got Milk? Err, I Mean Flickering?

If you happen to be one of those people with an insanely fast processor, or plan to release your code, you will probably want to add in *ONE* more function. Even though we're writing _ALL_ of the video buffer to the screen at once, it is still not 100% instantanious. And if your video card is pumping out frames faster than your monitor's refresh rate, you will experience what we call 'Flickering', or 'Snow' for you EGA guys out there. This is easily dealt with though. It is caused when the screen is being updated in the middle of a screen write, causing an inconsistant screen. Luckily, Card manufacturers were kind enough to give us access to determine if the electron bombarder is currently drawing, or retracing back to the top-left corner of the screen, which is when you want to update your screen.

The function to do this looks like this:

void vsync(void)
{
    // wait for verticle retrace to avoid flicker
    while (inportb(0x3da) & 0x08);
    /* vga is in retrace */
    while (!(inportb(0x3da) & 0x08));
    /* wait for start of retrace */
}

Then all you need to do is modify your do-while loop in main() to look like this:

do
{
    set_hot(); // generate random hotspots
    mk_lines(); // do all the math and screen updating
    vsync();
    flip(); // move your new frame to the video screen
} while(!kbhit());

Graduation

And there you go! That's as far as this document is going to take you. There are obviously some optimizations that can be done to this code. For instance, the process of averaging EVERY SINGLE PIXEL and dividing by 7 isn't very nice on your cpu. One way to overcome this would be to add an 8th value in the averaging and instead of dividing by 7, divide by 8, _BUT_ not by ' foo / 8' because that would be just as slow as now, slower even. By shifting the bits 3 places to the right, you accomplish the same thing as by dividing by 8. Soooo,

foo = (ave1 + ave2 ... ave8) >> 3;

instead of

foo = (ave1 + ave2 ... ave7) / 7;

This is not the only optimization that can be done, but it's the only one I'm going to cover in this article. The rest is left to your imagination and creativity. Try experimenting with different palette values, or hotspot percentages. Maybe instead of drawing directly above, you could draw up one and 2 to the right, giving it a 'windy' effect.. The possibilities are only limited to what you can come up with.

This document may be (and is ENCOURAGED TO BE!) freely distributed as long as it stays in its original form. I wrote this because quite franky all of the fire tutorials our there, ALL OF THEM, aren't worth a shit. And it's about time someone released one that actually bothers to explain _HOW_ to do the things instead of only giving a brief overview of _WHAT_ needs to be done to accomplish it.

Discuss this article in the forums


Date this article was posted to GameDev.net: 1/15/2001
(Note that this date does not necessarily correspond to the date the article was written)

See Also:
General

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