JavaScript EditorFree JavaScript Editor     Ajax Editor 



Main Page
  Previous Section Next Section

Color Effects

The next subject I want to discuss is color animation and tricks. In the past, 256-color palettized modes were the only bit depths available, and a lot of tricks and techniques were invented to take advantage of the instantaneous nature of color changes—that is, a change to one or more of the palette registers is instantly visible on the screen. These days 256-color modes are fading away due to faster hardware and acceleration. However, learning these techniques is still crucial to understanding other related concepts, not to mention that it will be many years until all games are totally RGB. There are a lot of 486 machines and even slower-MHz Pentiums still around that can handle only 256-color modes at any sort of reasonable speed!

Color Animation in 256-Color Modes

Color animation basically refers to any operation of modifying or shifting around color palette entries on-the-fly. For example, glowing objects, blinking lights, and many other effects can be created simply by manipulating the entries of the color table on-the-fly. The cool thing is that any object on the screen that has pixels with the values that you're manipulating in the color table will be affected.

Imagine how hard it would be to do this with bitmaps. For example, let's say you had a little ship with running lights on it, and you wanted them to blink. You would need a bitmap for each frame of animation. But with color animation, all you need to do is draw a single bitmap, make the lights a specific color index, and then animate the color index. Figure 7.36 illustrates this indirection graphically.

Figure 7.36. Color animation using palette indirection.

graphics/07fig36.gif

Two of my favorite effects are blinking and glowing colors. Let's begin with a blinking light function. Here's the functionality you want:

  • Creating up to 256 blinking lights.

  • Each light has an on and off color, in addition to a time delay measured in cycles for the on and off state.

  • Turning on or off any blinking light at any time with an ID, and/or resetting its parameters.

  • Terminating any blinking light and reusing its data storage.

This is a perfect example for showing some persistent data techniques and showing how to update DirectDraw palette entries. My strategy will be to create a single function that's called to create and destroy the lights as well as to perform the animation. The function will use static data arrays that are local and the function will have a number of operation modes:

BLINKER_ADD— Used to add a blinking color to the database. When called, the function returns an ID number used to reference the blinking light. System holds up to 256 lights.

BLINKER_DELETE— Deletes a blinking light of the sent ID.

BLINKER_UPDATE— Updates the on/off parameters of the sent ID's light.

BLINKER_RUN— Processes all the lights through one cycle.

The data structure used to hold a single light and also to create one is called BLINKER and is shown here:

// blinking light structure
typedef struct BLINKER_TYP
               {
               // user sets these
               int color_index;         // index of color to blink
               PALETTEENTRY on_color;   // RGB value of "on" color
               PALETTEENTRY off_color;  // RGB value of "off" color
               int on_time;             // number of frames to keep "on"
               int off_time;            // number of frames to keep "off"

               // internal member
               int counter;             // counter for state transitions
               int state;               // state of light,
                                        // -1 off, 1 on, 0 dead
               } BLINKER, *BLINKER_PTR;

Basically, you fill in the "user" fields and then call the function with a BLINKER_ADD command. Anyway, the general operation is as follows: You call the function at any time to add, delete, or update, but only once per frame with the run command. Here's the code for the function:

int Blink_Colors(int command, BLINKER_PTR new_light, int id)
{
// this function blinks a set of lights

static BLINKER lights[256]; // supports up to 256 blinking lights
static int initialized = 0; // tracks if function has initialized

// test if this is the first time function has run
if (!initialized)
   {
   // set initialized
   initialized = 1;

   // clear out all structures
   memset((void *)lights,0, sizeof(lights));

   } // end if

// now test what command user is sending
switch (command)
       {
 // add a light to the database
            {
            // run thru database and find an open light
            for (int index=0; index < 256; index++)
                {
                // is this light available?
                if (lights[index].state == 0)
                   {
                   // set light up
                   lights[index] = *new_light;

                   // set internal fields up
                   lights[index].counter =  0;
                   lights[index].state   = -1; // off

                   // update palette entry
                   lpddpal->SetEntries(0,lights[index].color_index,
                                       1,&lights[index].off_color);

                   // return id to caller
                   return(index);

                   } // end if

                } // end for index

            } break;

       case BLINKER_DELETE: // delete the light indicated by id
            {
            // delete the light sent in id
            if (lights[id].state != 0)
               {
               // kill the light
               memset((void *)&lights[id],0,sizeof(BLINKER));

               // return id
               return(id);

               } // end if
            else
                return(-1); // problem


            } break;


       case BLINKER_UPDATE: // update the light indicated by id
            {
            // make sure light is active
            if (lights[id].state != 0)
               {
               // update on/off parms only
               lights[id].on_color  = new_light->on_color;
               lights[id].off_color = new_light->off_color;
               lights[id].on_time   = new_light->on_time;
               lights[id].off_time  = new_light->off_time;

               // update palette entry
               if (lights[id].state == -1)
                  lpddpal->SetEntries(0,lights[id].color_index,
                                      1,&lights[id].off_color);
               else
                  lpddpal->SetEntries(0,lights[id].color_index,
                                      1,&lights[id].on_color);

               // return id
               return(id);

               } // end if
            else
                return(-1); // problem

            } break;

       case BLINKER_RUN: // run the algorithm
            {
            // run thru database and process each light
            for (int index=0; index < 256; index++)
                {
                // is this active?
                if (lights[index].state == -1)
                   {
                   // update counter
                   if (++lights[index].counter >= lights[index].off_time)
                      {
                      // reset counter
                      lights[index].counter = 0;

                      // change states
                      lights[index].state = -lights[index].state;

                      // update color
                      lpddpal->SetEntries(0,lights[index].color_index,
                                          1,&lights[index].on_color);

                      } // end if

                   } // end if
                else
                if (lights[index].state == 1)
                   {
                   // update counter
                   if (++lights[index].counter >= lights[index].on_time)
                      {
                      // reset counter
                      lights[index].counter = 0;

                      // change states
                      lights[index].state = -lights[index].state;
                      // update color
                      lpddpal->SetEntries(0,lights[index].color_index,
                                          1,&lights[index].off_color);

                      } // end if
                   } // end else if

                } // end for index

            } break;

       default: break;

       } // end switch

// return success
return(1);

} // end Blink_Colors

NOTE

I've boldfaced the sections that update the DirectDraw palette entries. I assume that there's a global palette interface lpddpal.


The function has three main sections: initialization, updating, and run logic. When the function is called for the first time, it initializes itself. Then the next code segment tests for updating commands or the run command. If an update-type command is requested, logic is performed to add, delete, or update a blinking light. If the run mode is requested, the lights are all processed through one cycle. In general, you would use the function after first adding one or more lights, which you'd do by setting up a generic BLINKER structure and then passing the structure to the function with the BLINKER_ADD command. The function would then return the ID of your blinking light, which you'd save—you'll need the ID if you want to delete or update a blinking light.

After you've created all the lights you want, you can call the function with all NULLs except for the command, which is BLINKER_RUN. You do this for each frame of your game loop. For example, let's say you have a game that runs at 30fps, and you want a red light to blink with a 50-50 duty cycle—one second on, one second off—along with a green light with a 50-50 duty cycle of two seconds on and two seconds off. Furthermore, you want to use palette entries 250 and 251 for the red and green light, respectively. Here's the code you need:

BLINKER temp; // used to hold temp info

PALETTEENTRY red   = {255,0,0,PC_NOCOLLAPSE};
PALETTEENTRY green = {0,255,0,PC_NOCOLLAPSE};
PALETTEENTRY black = {0,0,0,PC_NOCOLLAPSE};

// add red light
temp.color_index = 250;
temp.on_color    = red;
temp.off_color   = black;
temp.on_time     = 30; // 30 cycles at 30fps = 1 sec
temp.off_time    = 30;

// make call
int red_id = Blink_Colors(BLINKER_ADD, &temp, 0);

// now create green light
temp.color_index = 251;
temp.on_color    = green;
temp.off_color   = black;
temp.on_time     = 60; // 30 cycles at 30fps = 2 secs
temp.off_time    = 60;

// make call
int green_id = Blink_Colors(BLINKER_ADD, &temp, 0);

Now you're ready to rock and roll! In the main part of your game loop, you would make a call to Blink_Colors() each cycle, something like this:

// enter main event loop
while(TRUE)
        {
    // test if there is a message in queue, if so get it
        if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
           {
           // test if this is a quit
       if (msg.message == WM_QUIT)
           break;

           // translate any accelerator keys
           TranslateMessage(&msg);

           // send the message to the window proc
           DispatchMessage(&msg);
           } // end if

       // main game processing goes here
       Game_Main();

       // blink all the colors
       // could put this into Game_Main() also – better idea
       Blink_Colors(BLINKER_RUN, NULL, 0);

        } // end while

Of course, you can delete a blinker with its ID at any time, and it won't be processed anymore. For example, if you want to kill the red light:

Blink_Colors(BLINKER_DELETE, NULL, red_id);

It's as simple as that. And of course, you can update a blinker's on/off time and color values by setting up another BLINKER structure and then making the call with BLINKER_UPDATE. For example, if you want to alter the green blinker's parameters:

// set new parms
temp.on_time   = 100;
temp_off_time  = 200;
temp.on_color  = {255,255,0,PC_NOCOLLAPSE};
temp.off_color = {0,0,0,PC_NOCOLLAPSE};

// update blinker
Blink_Colors(BLINKER_UPDATE, temp, green_id);

That's enough of that! Check out DEMO7_15.CPP|EXE, which uses the Blink_Colors() function to make some of the lights on the starship image blink.

Color Rotation in 256-Color Modes

The next interesting color animation effect is called color rotation or color shifting. Basically, it's the process of taking a collection of adjacent color entries or registers and shifting them in a circular manner, as shown in Figure 7.37. Using this technique, you can make objects seem as if they're moving or shifting without writing a single pixel to the screen. It's great for simulating water or the motion of fluids. In addition, you can draw a number of images at different positions, each with a different color index. Then, if you rotate the colors, it will look like the object is moving. Great 3D Star Wars trenches can be created like this.

Figure 7.37. Color rotation.

graphics/07fig37.gif

The code for color rotation is fairly trivial. Algorithmically, to rotate color[c1] to color[c2], use the following code:

temp = color[c1];

for (int index = c1; index < c2; index++)
    color[c1] = color[c1+1];

// finish the cycle, close the loop
color[index] = temp;

Here's a function that implements the algorithm that I'm using for our library:

int Rotate_Colors(int start_index, int end_index)
{
// this function rotates the color between start and end

int colors = end_index - start_index + 1;

PALETTEENTRY work_pal[MAX_COLORS_PALETTE]; // working palette

// get the color palette
lpddpal->GetEntries(0,start_index,colors,work_pal);

// shift the colors
lpddpal->SetEntries(0,start_index+1,colors-1,work_pal);

// fix up the last color
lpddpal->SetEntries(0,start_index,1,&work_pal[colors - 1]);

// update shadow palette
lpddpal->GetEntries(0,0,MAX_COLORS_PALETTE,palette);

// return success
return(1);

} // end Rotate_Colors

Basically, the algorithm takes the starting and ending color index that you want to rotate and performs the rotation. Don't worry about the "shadow palettes" stuff; this is a library thing, so just focus on the logic. The interesting thing is how the algorithm works. It does the same thing as the FOR loop version, but in a different way. This is possible via an in-place shift. Anyway, for a demo of the function, take a look at DEMO7_16.CPP|EXE. It uses the function to create a moving stream of acid—water is for wimps!

Tricks with RGB Modes

The problem with RGB modes is that there isn't any color indirection. In other words, every pixel on the screen has its own RGB values, so there's no way to make a single change that affects the entire image. However, there are two ways to perform color-related processing:

  • Using manual color transformations or lookup tables.

  • Using the new DirectX color and gamma correction subsystems that perform real-time color manipulation on the primary surface.

TRICK

Gamma correction deals with the nonlinear response of a computer monitor to the input drive. In most cases, gamma correction allows you to modify the intensity or the brightness of the video signal. However, gamma correction can be performed separately on each red, green, and blue channel to obtain interesting effects.


      Previous Section Next Section
    



    JavaScript EditorAjax Editor     JavaScript Editor