Home > Articles

  • Print
  • + Share This
This chapter is from the book

The T3DLIB1 Library

At this point, we're ready to take a look at all the #defines, macros, data structures, and functions that compose the graphics module API of the T3DLIB game library—T3DLIB1.CPP.

NOTE

If you have already read the first Tricks, you will notice that the engine is almost identical except for the additional 16-bit support sprinkled throughout, along with windowed support. However, the code is still compatible, so you can take the demos from the first Tricks and compile them with this new version of T3DLIB1.CPP and they will still work! And of course, everything will compile with DirectX 8.0 or 9.0+.

The module consists of two files: T3DLIB1.CPP|H. Hence, you simply link these into your programs and then use the API.

The DirectX Graphics Engine Architecture

T3DLIB1 is a fairly simple 2D engine, as shown in Figure 3.9. Basically, it's a 2D, 8–16-bit color, doubled-buffered DirectX engine that has support for any resolution along with clipping. The engine also supports both windowed and full-screen displays, and takes care of all the setup. So, whether you're in a windowed display or full-screen mode, you always write to the offscreen or secondary buffer, and logic takes care of the detailed process of copying or flipping the secondary buffer to the primary display. This is done regardless of whether the primary display window is a window or full-screen.

To build an application using the library, you'll need to include T3DLIB1.CPP|H, along with DDRAW.LIB (DirectDraw Library) and WINMM.LIB (Win32 Multimedia Library).

NOTE

You only need WINMM.LIB if you are using Visual C++.

Figure 3.9Figure 3.9 The architecture of the graphics engine.

Basic Definitions

The engine has one header file, T3DLIB1.H, and within are a number of #defines that the engine uses. Here they are for your reference:

// watch for multiple inclusions
#ifndef T3DLIB1
#define T3DLIB1

// DEFINES ////////////////////////////////////////////////

// default screen values, these are all overriden by the
// call to DDraw_Init() and are just here to have something
// to set the globals to instead of constant values
#define SCREEN_WIDTH  640 // size of screen
#define SCREEN_HEIGHT  480
#define SCREEN_BPP   8 // bits per pixel
#define MAX_COLORS_PALETTE 256


#define DEFAULT_PALETTE_FILE "PALDATA2.PAL"

// used for selecting full screen/windowed mode
#define SCREEN_FULLSCREEN 0
#define SCREEN_WINDOWED  1

// bitmap defines
#define BITMAP_ID   0x4D42 // universal id for a bitmap
#define BITMAP_STATE_DEAD 0
#define BITMAP_STATE_ALIVE 1
#define BITMAP_STATE_DYING 2
#define BITMAP_ATTR_LOADED 128

#define BITMAP_EXTRACT_MODE_CELL 0
#define BITMAP_EXTRACT_MODE_ABS 1

// directdraw pixel format defines, used to help
// bitmap loader put data in proper format
#define DD_PIXEL_FORMAT8  8
#define DD_PIXEL_FORMAT555  15
#define DD_PIXEL_FORMAT565  16
#define DD_PIXEL_FORMAT888  24
#define DD_PIXEL_FORMATALPHA888 32

// defines for BOBs
#define BOB_STATE_DEAD   0 // this is a dead bob
#define BOB_STATE_ALIVE  1 // this is a live bob
#define BOB_STATE_DYING  2 // this bob is dying
#define BOB_STATE_ANIM_DONE 1 // done animation state
#define MAX_BOB_FRAMES   64 // maximum number of bob frames
#define MAX_BOB_ANIMATIONS  16 // maximum number of animation sequences

#define BOB_ATTR_SINGLE_FRAME 1 // bob has single frame
#define BOB_ATTR_MULTI_FRAME 2 // bob has multiple frames
#define BOB_ATTR_MULTI_ANIM  4 // bob has multiple animations
#define BOB_ATTR_ANIM_ONE_SHOT 8 // bob will perform the animation once
#define BOB_ATTR_VISIBLE  16 // bob is visible
#define BOB_ATTR_BOUNCE   32 // bob bounces off edges
#define BOB_ATTR_WRAPAROUND  64 // bob wraps around edges
#define BOB_ATTR_LOADED   128 // the bob has been loaded
#define BOB_ATTR_CLONE   256 // the bob is a clone

// screen transition commands (256 color modes only)
#define SCREEN_DARKNESS 0   // fade to black
#define SCREEN_WHITENESS 1   // fade to white
#define SCREEN_SWIPE_X 2   // do a horizontal swipe
#define SCREEN_SWIPE_Y 3   // do a vertical swipe
#define SCREEN_DISOLVE 4   // a pixel disolve
#define SCREEN_SCRUNCH 5   // a square compression
#define SCREEN_BLUENESS 6   // fade to blue
#define SCREEN_REDNESS 7   // fade to red
#define SCREEN_GREENNESS 8   // fade to green

// defines for Blink_Colors
#define BLINKER_ADD   0 // add a light to database
#define BLINKER_DELETE  1 // delete a light from database
#define BLINKER_UPDATE  2 // update a light
#define BLINKER_RUN   3 // run normal

// pi defines
#define PI   ((float)3.141592654f)
#define PI2  ((float)6.283185307f)
#define PI_DIV_2 ((float)1.570796327f)
#define PI_DIV_4 ((float)0.785398163f)
#define PI_INV  ((float)0.318309886f)

// fixed point mathematics constants
#define FIXP16_SHIFT  16
#define FIXP16_MAG  65536
#define FIXP16_DP_MASK 0x0000ffff
#define FIXP16_WP_MASK 0xffff0000
#define FIXP16_ROUND_UP 0x00008000

Working Macros

Next are all the macros we've written thus far. Again, you've seen them all in one place or another, but here they all are at once:

// these read the keyboard asynchronously
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// this builds a 16 bit color value in 5.5.5 format (1-bit alpha mode)
#define _RGB16BIT555(r,g,b) ((b & 31) + ((g & 31) << 5) + ((r & 31) << 10))

// this builds a 16 bit color value in 5.6.5 format (green dominate mode)
#define _RGB16BIT565(r,g,b) ((b & 31) + ((g & 63) << 5) + ((r & 31) << 11))

// this builds a 24 bit color value in 8.8.8 format
#define _RGB24BIT(a,r,g,b) ((b) + ((g) << 8) + ((r) << 16) )

// this builds a 32 bit color value in A.8.8.8 format (8-bit alpha mode)
#define _RGB32BIT(a,r,g,b) ((b) + ((g) << 8) + ((r) << 16) + ((a) << 24))

// bit manipulation macros
#define SET_BIT(word,bit_flag) ((word)=((word) | (bit_flag)))
#define RESET_BIT(word,bit_flag) ((word)=((word) & (~bit_flag)))

// initializes a direct draw struct,
// basically zeros it and sets the dwSize field
#define DDRAW_INIT_STRUCT(ddstruct) { memset(&ddstruct,0,sizeof(ddstruct));ddstruct.dwSize=sizeof(ddstruct); }

// used to compute the min and max of two expresions
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define MAX(a, b) (((a) > (b)) ? (b) : (a))

// used for swapping algorithm
#define SWAP(a,b,t) {t=a; a=b; b=t;}

// some math macros
#define DEG_TO_RAD(ang) ((ang)*PI/180)
#define RAD_TO_DEG(rads) ((rads)*180/PI)

#define RAND_RANGE(x,y) ( (x) + (rand()%((y)-(x)+1)))

Data Types and Structures

The next set of code elements are the types and data structures that the engine uses. Many of them aren't really of concern, because a lot of them are for the 2D aspect from the first Tricks, but it doesn't hurt to see all of it.

// basic unsigned types
typedef unsigned short USHORT;
typedef unsigned short WORD;
typedef unsigned char UCHAR;
typedef unsigned char BYTE;
typedef unsigned int QUAD;
typedef unsigned int UINT;

// container structure for bitmaps .BMP file
typedef struct BITMAP_FILE_TAG
{
BITMAPFILEHEADER bitmapfileheader; // contains the bitmapfile 
_header
BITMAPINFOHEADER bitmapinfoheader; //info including 
_the palette
PALETTEENTRY  palette[256];  // palette 
_stored here
UCHAR   *buffer;   // pointer to the data

  } BITMAP_FILE, *BITMAP_FILE_PTR;

// the blitter object structure BOB
typedef struct BOB_TYP
  {
  int state;   // the state of the object (general)
  int anim_state;  // an animation state variable, up to you
  int attr;   // attributes pertaining to the object (general)
  float x,y;   // position bitmap will be displayed at
  float xv,yv;  // velocity of object
  int width, height; // the width and height of the bob
  int width_fill;  // internal, used to force 8*x wide surfaces
  int bpp;   // bits per pixel
  int counter_1;  // general counters
  int counter_2;
  int max_count_1; // general threshold values;
  int max_count_2;
  int varsI[16];  // stack of 16 integers
  float varsF[16]; // stack of 16 floats
  int curr_frame;  // current animation frame
  int num_frames;  // total number of animation frames
  int curr_animation; // index of current animation
  int anim_counter; // used to time animation transitions
  int anim_index;  // animation element index
  int anim_count_max; // number of cycles before animation
  int *animations[MAX_BOB_ANIMATIONS]; // animation sequences

  // the bitmap images DD surfaces
  LPDIRECTDRAWSURFACE7 images[MAX_BOB_FRAMES]; 
  } BOB, *BOB_PTR;

// the simple bitmap image
typedef struct BITMAP_IMAGE_TYP
  {
  int state;   // state of bitmap
  int attr;   // attributes of bitmap
  int x,y;   // position of bitmap
  int width, height; // size of bitmap
  int num_bytes;  // total bytes of bitmap
  int bpp;   // bits per pixel
  UCHAR *buffer;  // pixels of bitmap

  } BITMAP_IMAGE, *BITMAP_IMAGE_PTR;

// 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;

// a 2D vertex
typedef struct VERTEX2DI_TYP
  {
  int x,y; // the vertex
  } VERTEX2DI, *VERTEX2DI_PTR;

// a 2D vertex
typedef struct VERTEX2DF_TYP
  {
  float x,y; // the vertex
  } VERTEX2DF, *VERTEX2DF_PTR;

// a 2D polygon
typedef struct POLYGON2D_TYP
  {
  int state;  // state of polygon
  int num_verts; // number of vertices
  int x0,y0;  // position of center of polygon
  int xv,yv;  // initial velocity
  DWORD color;  // could be index or PALETTENTRY
  VERTEX2DF *vlist; // pointer to vertex list

  } POLYGON2D, *POLYGON2D_PTR;



// matrix defines
typedef struct MATRIX3X3_TYP
  {
  union
  {
  float M[3][3]; // array indexed data storage

  // storage in row major form with explicit names
  struct
    {
    float M00, M01, M02;
    float M10, M11, M12;
    float M20, M21, M22;
    }; // end explicit names

  }; // end union
  } MATRIX3X3, *MATRIX3X3_PTR;

typedef struct MATRIX1X3_TYP
  {
  union
  {
  float M[3]; // array indexed data storage

  // storage in row major form with explicit names
  struct
    {
    float M00, M01, M02;

    }; // end explicit names
  }; // end union
  } MATRIX1X3, *MATRIX1X3_PTR;

typedef struct MATRIX3X2_TYP
  {
  union
  {
  float M[3][2]; // array indexed data storage

  // storage in row major form with explicit names
  struct
    {
    float M00, M01;
    float M10, M11;
    float M20, M21;
    }; // end explicit names

  }; // end union
  } MATRIX3X2, *MATRIX3X2_PTR;

typedef struct MATRIX1X2_TYP
  {
  union
  {
  float M[2]; // array indexed data storage

  // storage in row major form with explicit names
  struct
    {
    float M00, M01;

    }; // end explicit names
  }; // end union
  } MATRIX1X2, *MATRIX1X2_PTR;

You'll notice some math support at the end. This is from the 2D polygon transformation support from the first Tricks. We are going to rewrite much of it when we create the new 3D math library.

Function Prototypes

Next I want you to take a look at the complete function prototype list, so you can see everything at once. I'm not going to cover every single function in the function API in the following sections because many functions are different versions of each other.

// DirectDraw functions
int DDraw_Init(int width, int height, int bpp, int windowed=0);
int DDraw_Shutdown(void);

LPDIRECTDRAWCLIPPER DDraw_Attach_Clipper(LPDIRECTDRAWSURFACE7 lpdds,
           int num_rects, LPRECT clip_list);

LPDIRECTDRAWSURFACE7 DDraw_Create_Surface(int width, int height,
         int mem_flags=0, USHORT color_key_value=0);

int DDraw_Flip(void);
int DDraw_Wait_For_Vsync(void);

int DDraw_Fill_Surface(LPDIRECTDRAWSURFACE7 lpdds, USHORT color,
      RECT *client=NULL);

UCHAR *DDraw_Lock_Surface(LPDIRECTDRAWSURFACE7 lpdds,int *lpitch);
int DDraw_Unlock_Surface(LPDIRECTDRAWSURFACE7 lpdds);
UCHAR *DDraw_Lock_Primary_Surface(void);
int DDraw_Unlock_Primary_Surface(void);
UCHAR *DDraw_Lock_Back_Surface(void);
int DDraw_Unlock_Back_Surface(void);

// BOB functions
int Create_BOB(BOB_PTR bob,int x, int y,int width, int height,
    int num_frames,int attr,
    int mem_flags=0, USHORT color_key_value=0, int bpp=8);

int Clone_BOB(BOB_PTR source, BOB_PTR dest);
int Destroy_BOB(BOB_PTR bob);
int Draw_BOB(BOB_PTR bob, LPDIRECTDRAWSURFACE7 dest);

int Draw_Scaled_BOB(BOB_PTR bob, int swidth, int sheight,
     LPDIRECTDRAWSURFACE7 dest);

int Draw_BOB16(BOB_PTR bob, LPDIRECTDRAWSURFACE7 dest);

int Draw_Scaled_BOB16(BOB_PTR bob, int swidth, int sheight,
      LPDIRECTDRAWSURFACE7 dest);

int Load_Frame_BOB(BOB_PTR bob, BITMAP_FILE_PTR bitmap,
     int frame, int cx,int cy,int mode);

int Load_Frame_BOB16(BOB_PTR bob, BITMAP_FILE_PTR bitmap,
      int frame, int cx,int cy,int mode);

int Animate_BOB(BOB_PTR bob);
int Move_BOB(BOB_PTR bob);

int Load_Animation_BOB(BOB_PTR bob, int anim_index,
      int num_frames, int *sequence);

int Set_Pos_BOB(BOB_PTR bob, int x, int y);
int Set_Vel_BOB(BOB_PTR bob,int xv, int yv);
int Set_Anim_Speed_BOB(BOB_PTR bob,int speed);
int Set_Animation_BOB(BOB_PTR bob, int anim_index);
int Hide_BOB(BOB_PTR bob);
int Show_BOB(BOB_PTR bob);
int Collision_BOBS(BOB_PTR bob1, BOB_PTR bob2);

// general utility functions
DWORD Get_Clock(void);
DWORD Start_Clock(void);
DWORD Wait_Clock(DWORD count);

int Collision_Test(int x1, int y1, int w1, int h1,
     int x2, int y2, int w2, int h2);

int Color_Scan(int x1, int y1, int x2, int y2,
    UCHAR scan_start, UCHAR scan_end,
    UCHAR *scan_buffer, int scan_lpitch);

int Color_Scan16(int x1, int y1, int x2, int y2,
     USHORT scan_start, USHORT scan_end,
     UCHAR *scan_buffer, int scan_lpitch);

// graphics functions
int Draw_Clip_Line(int x0,int y0, int x1, int y1, int color,
     UCHAR *dest_buffer, int lpitch);

int Draw_Clip_Line16(int x0,int y0, int x1, int y1, int color,
      UCHAR *dest_buffer, int lpitch);

int Clip_Line(int &x1,int &y1,int &x2, int &y2);

int Draw_Line(int xo, int yo, int x1,int y1, int color,
    UCHAR *vb_start,int lpitch);

int Draw_Line16(int xo, int yo, int x1,int y1, int color,
    UCHAR *vb_start,int lpitch);

int Draw_Pixel(int x, int y,int color,
    UCHAR *video_buffer, int lpitch);

int Draw_Rectangle(int x1, int y1, int x2, int y2,
     int color,LPDIRECTDRAWSURFACE7 lpdds);

void HLine(int x1,int x2,int y,int color, UCHAR *vbuffer, int lpitch);
void VLine(int y1,int y2,int x,int color, UCHAR *vbuffer, int lpitch);
void HLine16(int x1,int x2,int y,int color, UCHAR *vbuffer, int lpitch);
void VLine16(int y1,int y2,int x,int color, UCHAR *vbuffer, int lpitch);
void Screen_Transitions(int effect, UCHAR *vbuffer, int lpitch);
int Draw_Pixel(int x, int y,int color,UCHAR *video_buffer, int lpitch);
int Draw_Pixel16(int x, int y,int color,UCHAR *video_buffer, int lpitch);

// palette functions
int Set_Palette_Entry(int color_index, LPPALETTEENTRY color);
int Get_Palette_Entry(int color_index, LPPALETTEENTRY color);
int Load_Palette_From_File(char *filename, LPPALETTEENTRY palette);
int Save_Palette_To_File(char *filename, LPPALETTEENTRY palette);
int Save_Palette(LPPALETTEENTRY sav_palette);
int Set_Palette(LPPALETTEENTRY set_palette);
int Rotate_Colors(int start_index, int end_index);
int Blink_Colors(int command, BLINKER_PTR new_light, int id);

// simple bitmap image functions
int Create_Bitmap(BITMAP_IMAGE_PTR image, int x, int y,
     int width, int height, int bpp=8);

int Destroy_Bitmap(BITMAP_IMAGE_PTR image);

int Draw_Bitmap(BITMAP_IMAGE_PTR source_bitmap,UCHAR *dest_buffer,
    int lpitch, int transparent);

int Draw_Bitmap16(BITMAP_IMAGE_PTR source_bitmap,UCHAR *dest_buffer,
     int lpitch, int transparent);

int Load_Image_Bitmap(BITMAP_IMAGE_PTR image,BITMAP_FILE_PTR bitmap,
      int cx,int cy,int mode);

int Load_Image_Bitmap16(BITMAP_IMAGE_PTR image,BITMAP_FILE_PTR bitmap,
      int cx,int cy,int mode);

int Scroll_Bitmap(BITMAP_IMAGE_PTR image, int dx, int dy=0);

int Copy_Bitmap(BITMAP_IMAGE_PTR dest_bitmap, int dest_x, int dest_y,
    BITMAP_IMAGE_PTR source_bitmap, int source_x, int source_y,
    int width, int height);

int Flip_Bitmap(UCHAR *image, int bytes_per_line, int height);

// bitmap file functions
int Load_Bitmap_File(BITMAP_FILE_PTR bitmap, char *filename);
int Unload_Bitmap_File(BITMAP_FILE_PTR bitmap);

// gdi functions
int Draw_Text_GDI(char *text, int x,int y,
     COLORREF color, LPDIRECTDRAWSURFACE7 lpdds);

int Draw_Text_GDI(char *text, int x,int y,
     int color, LPDIRECTDRAWSURFACE7 lpdds);

// error functions
int Open_Error_File(char *filename, FILE *fp_override=NULL);
int Close_Error_File(void);
int Write_Error(char *string, ...);

// 2d 8-bit, 16-bit triangle rendering
void Draw_Top_Tri(int x1,int y1,int x2,int y2, int x3,int y3,
     int color,UCHAR *dest_buffer, int mempitch);

void Draw_Bottom_Tri(int x1,int y1, int x2,int y2, int x3,int y3,
      int color,UCHAR *dest_buffer, int mempitch);

void Draw_Top_Tri16(int x1,int y1,int x2,int y2, int x3,int y3,
     int color,UCHAR *dest_buffer, int mempitch);

void Draw_Bottom_Tri16(int x1,int y1, int x2,int y2, int x3,int y3,
      int color,UCHAR *dest_buffer, int mempitch);

void Draw_Top_TriFP(int x1,int y1,int x2,int y2, int x3,int y3,
     int color,UCHAR *dest_buffer, int mempitch);

void Draw_Bottom_TriFP(int x1,int y1, int x2,int y2, int x3,int y3,
      int color,UCHAR *dest_buffer, int mempitch);

void Draw_Triangle_2D(int x1,int y1,int x2,int y2,int x3,int y3,
      int color,UCHAR *dest_buffer, int mempitch);

void Draw_Triangle_2D16(int x1,int y1,int x2,int y2,int x3,int y3,
      int color,UCHAR *dest_buffer, int mempitch);

void Draw_TriangleFP_2D(int x1,int y1,int x2,int y2,int x3,int y3,
      int color,UCHAR *dest_buffer, int mempitch);

inline void Draw_QuadFP_2D(int x0,int y0,int x1,int y1,
       int x2,int y2,int x3, int y3,
       int color,UCHAR *dest_buffer, int mempitch);


// general 2D 8-bit, 16-bit polygon rendering and transforming functions
void Draw_Filled_Polygon2D(POLYGON2D_PTR poly, UCHAR *vbuffer, int mempitch);
void Draw_Filled_Polygon2D16(POLYGON2D_PTR poly, UCHAR *vbuffer, int mempitch);
int Translate_Polygon2D(POLYGON2D_PTR poly, int dx, int dy);
int Rotate_Polygon2D(POLYGON2D_PTR poly, int theta);
int Scale_Polygon2D(POLYGON2D_PTR poly, float sx, float sy);
void Build_Sin_Cos_Tables(void);
int Translate_Polygon2D_Mat(POLYGON2D_PTR poly, int dx, int dy);
int Rotate_Polygon2D_Mat(POLYGON2D_PTR poly, int theta);
int Scale_Polygon2D_Mat(POLYGON2D_PTR poly, float sx, float sy);
int Draw_Polygon2D(POLYGON2D_PTR poly, UCHAR *vbuffer, int lpitch);
int Draw_Polygon2D16(POLYGON2D_PTR poly, UCHAR *vbuffer, int lpitch);

// math functions
int Fast_Distance_2D(int x, int y);
float Fast_Distance_3D(float x, float y, float z);

// collision detection functions
int Find_Bounding_Box_Poly2D(POLYGON2D_PTR poly,
        float &min_x, float &max_x,
        float &min_y, float &max_y);

int Mat_Mul_1X2_3X2(MATRIX1X2_PTR ma,
     MATRIX3X2_PTR mb,
     MATRIX1X2_PTR mprod);

int Mat_Mul_1X3_3X3(MATRIX1X3_PTR ma,
     MATRIX3X3_PTR mb,
     MATRIX1X3_PTR mprod);

int Mat_Mul_3X3(MATRIX3X3_PTR ma,
    MATRIX3X3_PTR mb,
    MATRIX3X3_PTR mprod);

inline int Mat_Init_3X2(MATRIX3X2_PTR ma,
      float m00, float m01,
      float m10, float m11,
      float m20, float m21);


// memory manipulation functions
inline void Mem_Set_WORD(void *dest, USHORT data, int count);
inline void Mem_Set_QUAD(void *dest, UINT data, int count);

You'll notice that all the calls to any rasterization functions that take pointers to the frame buffer always take a UCHAR*. Additionally, all memory pitch parameters are always in terms of bytes. Remember, this was our convention. Also note that about 90% of the entire library supports 16-bit graphics, but some things only work for 8-bit color. Finally, there's a lot of 2D polygon functions in there. These are all from the first Tricks, and although we could just use them for this book, we are going to write all of them again from scratch. This is because they are part of the rasterization process in 3D graphics, and I'm going to have to think up new names for them!

Global Domination

You know that I like globals because they are so fast. Moreover, they are appropriate for a lot of system-level variables (which any game engine has a lot of), so here are the globals for the engine—again, I know this is hard to follow, but just try and get a feel for them. I have commented them to help out.

FILE *fp_error;      // general error file
char error_filename[80];    // error file name

// notice that interface 7.0 is used on a number of interfaces
LPDIRECTDRAW7  lpdd;   // dd object
LPDIRECTDRAWSURFACE7 lpddsprimary; // dd primary surface
LPDIRECTDRAWSURFACE7 lpddsback;  // dd back surface
LPDIRECTDRAWPALETTE lpddpal;  // a pointer to the created dd palette
LPDIRECTDRAWCLIPPER lpddclipper; // dd clipper for back surface
LPDIRECTDRAWCLIPPER lpddclipperwin; // dd clipper for window
PALETTEENTRY   palette[256]; // color palette
PALETTEENTRY   save_palette[256]; // used to save palettes
DDSURFACEDESC2  ddsd;  // a direct draw surface description struct
DDBLTFX    ddbltfx; // used to fill
DDSCAPS2    ddscaps; // a direct draw surface capabilities struct
HRESULT    ddrval; // result back from dd calls
UCHAR    *primary_buffer; // primary video buffer
UCHAR    *back_buffer; // secondary back buffer
int     primary_lpitch; // memory line pitch
int     back_lpitch;  // memory line pitch
BITMAP_FILE   bitmap8bit;  // a 8 bit bitmap file
BITMAP_FILE   bitmap16bit;  // a 16 bit bitmap file
BITMAP_FILE   bitmap24bit;  // a 24 bit bitmap file

DWORD start_clock_count; // used for timing
int windowed_mode;  // tracks if dd is windowed or not

// these defined the general clipping rectangle for software clipping
int min_clip_x, // clipping rectangle
 max_clip_x,
 min_clip_y,
 max_clip_y;

// these are overwritten globally by DD_Init()
int screen_width, // width of screen
 screen_height, // height of screen
 screen_bpp,  // bits per pixel
 screen_windowed; // is this a windowed app?

int dd_pixel_format; // default pixel format

int window_client_x0; // used to track the starting (x,y) client area for
int window_client_y0; // for windowed mode directdraw operations

// storage for our lookup tables
float cos_look[360];
float sin_look[360];

// function ptr to RGB16 builder that builds 5.5.5 or 5.6.5 data
// depending on mode
USHORT (*RGB16Bit)(int r, int g, int b);

Make note of all the global names of the DirectX interface. Also, take a look at the various clipping and screen size globals.

The DirectDraw Interface

Now that you've seen all the data support, let's take a look at all the DirectDraw support functions. The DirectDraw system from the first Tricks (with a little upgrading) has the following features:

  • Double-buffered, with both a primary and secondary (offscreen frame buffer)

  • 8-bit color with palette

  • 16-bit color with auto pixel format detection

  • Windowed support for both 8- and 16-bit color modes

  • 2D polygon and raster bitmap clipping

  • Access to both the primary and secondary buffers

  • Page flipping and buffer copying for windowed displays

NOTE

Note the last feature. DirectX does not allow Windowed applications to be double buffered, or in DirectX speak, to be complex surfaces. Therefore, when a windowed display is requested, instead of creating a standard full-screen complex surface with a primary buffer and a back buffer, only a primary buffer (which is the entire screen), and an offscreen plain buffer that's the size of the client area of the window are created. Then when a flip is requested, the logic manually copies the offscreen back buffer to the window's client area. Of course, this is all transparent to you.

Now let's take a look at each function, along with an example.

Function Prototype:

int DDraw_Init(int width, // width of display
    int height, // height of display
    int bpp, // bits per pixel
    int windowed=0); // 0 for full screen, 1 for windowed

Purpose:

DDraw_Init() starts up and initializes DirectDraw. You can send any resolution and color depth. Also, if you want a windowed display, send a 1 for the last parameter; otherwise it defaults to full-screen. Returns TRUE if successful.

Examples:

// put the system into full screen 800x600 with 256 colors
DDraw_Init(800,600,8);

// put the system into windowed mode screen 400x400 with 16-bit color
// note that the desktop must be in 16-bit color
DDraw_Init(400,400,16,1);

Function Prototype:

int DDraw_Shutdown(void);

Purpose:

DDraw_Shutdown() shuts down DirectDraw and releases all interfaces.

Example:

// in your system shutdown code you might put
DDraw_Shutdown();

Function Prototype:

LPDIRECTDRAWCLIPPER
 DDraw_Attach_Clipper(
 LPDIRECTDRAWSURFACE7 lpdds, // surface to attach to
 int num_rects,  // number of rects
 LPRECT clip_list); // pointer to rects

Purpose:

DDraw_Attach_Clipper() attaches a clipper to the sent surface (the back buffer in most cases). In addition, you must send the number of rectangles in the clipping list and a pointer to the RECT list itself. Returns TRUE if successful.

Example:

// creates a clipping region the size of the screen
RECT clip_zone = {0,0,SCREEN_WIDTH-1, SCREEN_HEIGHT-1};
DDraw_Attach_Clipper(lpddsback, 1, &clip_zone);

Function Prototype:

LPDIRECTDRAWSURFACE7
 DDraw_Create_Surface(int width, // width of surface
    int height,   // height of surface
    int mem_flags=0,  // control flags
    USHORT color_key_value=0); // color key value

Purpose:

DDraw_Create_Surface() is used to create a generic offscreen DirectDraw surface in system memory, VRAM, or AGP memory. The default is DDSCAPS_OFFSCREENPLAIN. Any additional control flags are logically ORed with the default. They are the standard DirectDraw DDSCAP* flags, such as DDSCAPS_SYSTEMMEMORY and DDSCAPS_VIDEOMEMORY for system memory and VRAM, respectively. The function has internal logic to create either an 8- or 16-bit surface based on the previously set graphics mode. Additionally, the last parameter controls the value of the color key, which defaults to (int)0. In 8-bit color mode, this equates to index 0; in 16-bit color mode, this equates to RGB 0.0.0. If you want another value to be used as the transparent color key, send the value to override the default. If the function is successful, it returns a pointer to the new surface; otherwise it returns NULL.

Examples:

// let's create a 64x64 surface in VRAM
LPDIRECTDRAWSURFACE7 image = DDraw_Create_Surface(64,64, DDSCAPS_VIDEOMEMORY);

// let's create a 128x128 surface in off screen memory with a color key of 16
// assuming this is a 256 color mode
LPDIRECTDRAWSURFACE7 image =
    DDraw_Create_Surface(128,128, DDSCAPS_OFFSCREENPLAIN ,16);

Function Prototype:

int DDraw_Flip(void);

Purpose:

DDraw_Flip() simply flips the primary surface with the secondary surface in full-screen mode; in windowed mode, it copies the virtual secondary or back buffer to the client area of the windowed display. The call waits until the flip can take place, so it might not return immediately. Returns TRUE if successful.

Example:

// flip em baby
DDraw_Flip();

Function Prototype:

int DDraw_Wait_For_Vsync(void);

Purpose:

DDraw_Wait_For_Vsync() waits until the next vertical blank period begins (when the raster hits the bottom of the screen). Returns TRUE if successful, FALSE if something really bad happened.

Example:

// wait 1/70th of sec
DDraw_Wait_For_Vsync();

Function Prototype:

int DDraw_Fill_Surface(LPDIRECTDRAWSURFACE7 lpdds, // surface to fill
      int color, // color to fill
         // either 8-bit index or 16-bit RGB
      RECT *client); // client area, null for whole surface

Purpose:

DDraw_Fill_Surface() is used to fill a surface with a color. The color must be in the color depth format of the surface. For example, a single byte in 256-color mode or an RGB descriptor in high-color modes. Also, if you want to fill a subregion of the entire surface, you can send a RECT* that defines it; otherwise, the entire surface is filled. Returns TRUE if successful.

Example:

// fill the primary surface with color 0
DDraw_Fill_Surface(lpddsprimary,0);

Function Prototype:

UCHAR *DDraw_Lock_Surface(LPDIRECTDRAWSURFACE7 lpdds,int *lpitch);

Purpose:

DDraw_Lock_Surface() locks the sent surface (if possible) and returns a UCHAR pointer to the surface, along with updating the sent lpitch variable with the linear memory pitch of the surface. While the surface is locked, you can manipulate it and write pixels to it, but the blitter will be blocked, so remember to unlock the surface ASAP. In addition, after unlocking the surface, the memory pointer and pitch are most likely invalid and should not be used. Returns the non-NULL address of the surface memory if successful, NULL otherwise.

Example:

// holds the memory pitch
int lpitch = 0;

// let's lock the little 64x64 image we made
UCHAR *memory = DDraw_Lock_Surface(image, &lpitch);

Function Prototype:

int DDraw_Unlock_Surface(LPDIRECTDRAWSURFACE7 lpdds);

Purpose:

DDraw_Unlock_Surface() unlocks a surface previously locked with DDraw_Lock_Surface(). You need only send the pointer to the surface. Returns TRUE if successful.

Example:

// unlock the image surface
DDraw_Unlock_Surface(image);

Function Prototype(s):

UCHAR *DDraw_Lock_Back_Surface(void);
UCHAR *DDraw_Lock_Primary_Surface(void);

Purpose:

These two functions are used to lock the primary and secondary rendering surfaces. However, in most cases you'll only be interested in locking the secondary surface, because this is a double-buffered system, but the capability to lock the primary surface is there if you need it. If you call DDraw_Lock_Primary_Surface(), the following globals will become valid:

extern UCHAR *primary_buffer;  // primary video buffer
extern int  primary_lpitch;  // memory line pitch

Then you are free to manipulate the surface memory as you want; however, the blitter will be blocked and all hardware acceleration is inhibited during a lock. Anyway, making the call to DDraw_Lock_Back_Surface() will lock the back buffer surface and validate the following globals:

extern UCHAR *back_buffer;   // secondary back buffer
extern int  back_lpitch;   // memory line pitch

NOTE

Do not change any of these globals yourself—they are used to track state changes in the locking functions. Changing them yourself might make the engine go crazy.

Example:

// let lock the primary surface and write a pixel to the
// upper left hand corner assuming an 8-bit mode
DDraw_Lock_Primary();

primary_buffer[0] = 100;

Function Prototype:

int DDraw_Unlock_Primary_Surface(void);
int DDraw_Unlock_Back_Surface(void);

Purpose:

These functions are used to unlock the primary or back buffer surfaces. If you try to unlock a surface that wasn't locked, there is no effect. Returns TRUE if successful.

Example:

// unlock the secondary back buffer
DDraw_Unlock_Back_Surface();

2D Polygon Functions

The next set of functions make up the 2D polygon system. This is by no means advanced, fast, or cutting edge, but it's great to build up demos. The functions work and do the job. However, there are better ways to do all of this stuff, but that's why you're glued to the book, right?

Function Prototype(s):

void Draw_Triangle_2D(int x1,int y1, // triangle vertices
     int x2,int y2,
     int x3,int y3,
     int color, // 8-bit color index
     UCHAR *dest_buffer, // destination buffer
     int mempitch); // memory pitch

// fixed point high speed version, slightly less accurate
void Draw_TriangleFP_2D(int x1,int y1,
      int x2,int y2,
      int x3,int y3,
      int color,
      UCHAR *dest_buffer,
      int mempitch);

// 16-bit version
void Draw_Triangle_2D16(int x1,int y1, // triangle vertices
     int x2,int y2,
     int x3,int y3,
     int color, // 16-bit RGB color
     UCHAR *dest_buffer, // destination buffer
     int mempitch); // memory pitch

Purpose:

Draw_Triangle_2D*() draws a filled triangle in the given memory buffer with the sent color. The triangle will be clipped to the current clipping region set in the globals, not by the DirectDraw clipper, because the function uses software and not the blitter to draw lines. Draw_TriangleFP_2D() does the exact same thing, but uses fixed-point math internally and is slightly faster, but with slightly less accuracy. All functions return nothing.

Examples:

// draw a triangle (100,10) (150,50) (50,60)
// with color index 50 in the back buffer surface
Draw_Triangle_2D(100,10,150,50,50,60,
     50, // color index 50
     back_buffer,
     back_lpitch);

// do the same with 16 bit version
// with RGB color pure red
Draw_Triangle_2D16(100,10,150,50,50,60,
     RGB16BIT565(31,0,0), // assume 5.6.5 format
     back_buffer,
     back_lpitch);

Function Prototype:

inline void Draw_QuadFP_2D(int x0,int y0, // vertices
   int x1,int y1,
   int x2,int y2,
   int x3,int y3,
   int color, // 8-bit color index
   UCHAR *dest_buffer, // destination video buffer
   int mempitch); // memory pitch of buffer

Purpose:

Draw_QuadFP_2D() draws the sent quadrilateral as a composition of two triangles in 8-bit mode only. Notice there isn't a 16-bit version. Returns nothing.

Example:

// draw a quadrilateral, note vertices must be ordered
// either in cw or ccw order
Draw_QuadFP_2D(0,0, 10,0, 15,20, 5,25,
    100,
    back_buffer, back_lpitch);

Function Prototype:

void Draw_Filled_Polygon2D(
   POLYGON2D_PTR poly, // poly to render
   UCHAR *vbuffer, // video buffer
   int mempitch); // memory pitch

// 16-bit version
void Draw_Filled_Polygon2D16(
   POLYGON2D_PTR poly, // poly to render
   UCHAR *vbuffer, // video buffer
   int mempitch); // memory pitch

Purpose:

Draw_Filled_Polygon2D*() draws a general filled polygon with n sides in either 8- or 16-bit color mode. The function simply takes the polygon to render, a pointer to the video buffer along with pitch, and that's it! Note that the function renders relative to the polys (x0,y0), so make sure these are initialized. Returns nothing.

Examples:

// draw a polygon in the primary buffer
// in 8-bit mode
Draw_Filled_Polygon2D(&poly,
      primary_buffer,
      primary_lpitch);

// draw a polygon in the primary buffer
// in 16-bit mode
Draw_Filled_Polygon2D16(&poly,
      primary_buffer,
      primary_lpitch);

Function Prototype:

int Translate_Polygon2D(
   POLYGON2D_PTR poly, // poly to translate
   int dx, int dy); // translation factors

Purpose:

Translate_Polygon2D() translates the given polygon's origin (x0,y0). Note that the function does not transform or modify the actual vertices making up the polygon. Returns TRUE if successful.

Example:

// translate polygon 10,-5
Translate_Polygon2D(&poly, 10, -5);

Function Prototype:

int Rotate_Polygon2D(
     POLYGON2D_PTR poly, // poly to rotate
     int theta); // angle 0-359

Purpose:

Rotate_Polygon2D() rotates the sent polygon in a counter-clockwise fashion about its origin. The angle must be an integer from 0–359. Returns TRUE if successful.

Example:

// rotate polygon 10 degrees
Rotate_Polygon2D(&poly, 10);

Function Prototype:

int Scale_Polygon2D(POLYGON2D_PTR poly, // poly to scale
     float sx, float sy); // scale factors

Purpose:

Scale_Polygon2D() scales the sent polygon by scale factors sx and sy in the x- and y-axes, respectively. Returns nothing.

Example:

// scale the poly equally 2x
Scale_Polygon2D(&poly, 2,2);

2D Graphic Primitives

This set of functions contains a few of everything—kind of a potpourri of graphics primitives. Nothing you haven't seen, at least I don't think so. Also, the 16-bit support is there, but not completely orthogonal to the 8-bit support.

Function Prototype:

int Draw_Clip_Line(int x0,int y0, // starting point
     int x1, int y1, // ending point
     int color,  // 8-bit color
     UCHAR *dest_buffer, // video buffer
     int lpitch); // memory pitch

// 16-bit version
int Draw_Clip_Line16(int x0,int y0, // starting point
     int x1, int y1, // ending point
     int color,  // 16-bit RGB color
     UCHAR *dest_buffer, // video buffer
     int lpitch); // memory pitch

Purpose:

Draw_Clip_Line*() clips the sent line to the current clipping rectangle and then draws a line in the sent buffer (8- or 16-bit). Returns TRUE if successful.

Examples:

// draw a line in the back buffer from (10,10) to (100,200)
//assume an 8-bit system
Draw_Clip_Line(10,10,100,200,
    5, // color index 5
    back_buffer,
    back_lpitch);

// draw a line in the back buffer from (10,10) to (100,200)
//assume an 16-bit system
Draw_Clip_Line16(10,10,100,200,
    RGB16BIT565(0,31,0), // RGB green
    back_buffer,
    back_lpitch);

Function Prototype:

int Clip_Line(int &x1,int &y1, // starting point
    int &x2, int &y2); // ending point

Purpose:

Clip_Line() is for the most part internal, but you can call it to clip the sent line to the current clipping rectangle. Note that the function modifies the sent endpoints, so save them if you don't want this side effect. Also, the function does NOT draw anything, it only clips the endpoints mathematically, so bit depth is irrelevant. Returns TRUE if successful.

Example:

// clip the line defined by x1,y1 to x2,y2
Clip_Line(x1,y1,x2,y2);

Function Prototype:

int Draw_Line(int xo, int yo, // starting point
    int x1,int y1, // ending point
    int color,  // 8-bit color index
    UCHAR *vb_start, // video buffer
    int lpitch);  // memory pitch



// 16-bit version
int Draw_Line16(int xo, int yo, // starting point
    int x1,int y1, // ending point
    int color,  // 16-bit RGB color
    UCHAR *vb_start, // video buffer
    int lpitch);  // memory pitch

Purpose:

Draw_Line*() draws a line without any clipping, so make sure that the endpoints are within the display surface's valid coordinates. This function is slightly faster than the clipped version, because the clipping operation is not needed. Returns TRUE if successful.

Example:

// draw a line in the back buffer from (10,10) to (100,200)
// 8-bit mode
Draw_Line(10,10,100,200,
   5, // color 5
   back_buffer,
   back_lpitch);

// draw a line in the back buffer from (10,10) to (100,200)
// 16-bit mode
Draw_Line16(10,10,100,200,
   RGB16BIT(31,31,31), // RGB white
   back_buffer,
   back_lpitch);

Function Prototype:

inline int Draw_Pixel(int x, int y, // position of pixel
      int color, // 8-bit color
      UCHAR *video_buffer, // gee hmm?
      int lpitch); // memory pitch

// 16-bit version
inline int Draw_Pixel16(int x, int y, // position of pixel
      int color, // 16-bit RGB color
      UCHAR *video_buffer, // gee hmm?
      int lpitch); // memory pitch

Purpose:

Draw_Pixel*() draws a single pixel on the sent display surface memory buffer. In most cases, you won't create objects based on pixels, because the overhead of the call itself takes more time than plotting the pixel. If speed isn't your concern, the function does the job—at least it's inline! Returns TRUE if successful.

Example:

// draw pixel with color index 100 in the center of the 640x480x8 screen
Draw_Pixel(320,240, 100, back_buffer, back_lpitch);

// draw blue pixel in the center of the 640x480x16 screen
Draw_Pixel16(320,240, RGB16BIT(0,0,31), back_buffer, back_lpitch);

Function Prototype:

int Draw_Rectangle(int x1, int y1, // upper left corner
    int x2, int y2, // lower right corner
    int color, // 8-bit color index or
       // 16-bit RGB word
    LPDIRECTDRAWSURFACE7 lpdds); // dd surface

Purpose:

Draw_Rectangle() draws a rectangle on the sent DirectDraw surface. Note that the surface must be unlocked for the call to work. Moreover, the function uses the blitter, so it's very fast, and it works in either 8- or 16-bit color mode. Returns TRUE if successful.

Example:

// fill the screen using the blitter
Draw_Rectangle(0,0,639,479,0,lpddsback);

Function Prototype:

void HLine(int x1,int x2, // start and end x points
   int y,   // row to draw on
   int color,  // 8-bit color
   UCHAR *vbuffer, // video buffer
   int lpitch); // memory pitch
void HLine16(int x1,int x2, // start and end x points
   int y,   // row to draw on
   int color,  // 16-bit RGB color
   UCHAR *vbuffer, // video buffer
   int lpitch); // memory pitch

Purpose:

HLine*() draws a horizontal line very quickly compared to the general line drawing function. Returns nothing.

Example:

// draw a fast line in 8-bit mode from 10,100 to 100,100 with color index 20
HLine(10,100,100,
  20, back_buffer, back_lpitch);
// draw a fast line in 16-bit mode from 10,100 to 100,100 with blue
HLine(10,100,100,
  RGB16Bit(0,0,255), back_buffer, back_lpitch);

Function Prototype:

void VLine(int y1,int y2, // start and end row
   int x,   // column to draw in
   int color,  // 8-bit color
   UCHAR *vbuffer,// video buffer
   int lpitch); // memory pitch
void VLine16(int y1,int y2, // start and end row
   int x,   // column to draw in
   int color,  // 16-bit RGB color
   UCHAR *vbuffer,// video buffer
   int lpitch); // memory pitch

Purpose:

VLine() draws a fast vertical line. It's not as fast as HLine(), but it is faster than Draw_Line(), so use it if you know a line is going to be vertical in all cases. Returns nothing.

Example:

// draw a 8-bit line from 320,0 to 320,479 with color index 54
VLine(0,479,320,54,
  primary_buffer,
  primary_lpitch);

// draw a 16-bit line from 320,0 to 320,479 with green
VLine(0,479,320,RGB16Bit(0,255,0),
  primary_buffer,
  primary_lpitch);

Function Prototype:

void Screen_Transitions(int effect, // screen transition
      UCHAR *vbuffer,// video buffer
      int lpitch); // memory pitch

// screen transition commands
#define SCREEN_DARKNESS 0   // fade to black
#define SCREEN_WHITENESS 1   // fade to white
#define SCREEN_SWIPE_X 2   // do a horizontal swipe
#define SCREEN_SWIPE_Y 3   // do a vertical swipe
#define SCREEN_DISOLVE 4   // a pixel disolve
#define SCREEN_SCRUNCH 5   // a square compression
#define SCREEN_BLUENESS 6   // fade to blue
#define SCREEN_REDNESS 7   // fade to red
#define SCREEN_GREENNESS 8   // fade to green

Purpose:

Screen_Transition() performs various in-memory screen transitions as listed in the preceding header information. Note that the transformations are destructive, so please save the image and/or palette if you need them after the transition. Note that this only works for 8-bit color modes. Returns nothing.

Example:

// fade the primary display screen to black
Screen_Transition(SCREEN_DARKNESS, NULL, 0);

Function Prototype(s):

int Draw_Text_GDI(char *text, // null terminated string
   int x,int y, // position
   COLORREF color, // general RGB color
   LPDIRECTDRAWSURFACE7 lpdds); // dd surface

int Draw_Text_GDI(char *text, // null terminated string
    int x,int y, // position
    int color, // 8-bit color index
    LPDIRECTDRAWSURFACE7 lpdds); // dd surface

Purpose:

Draw_Text_GDI() draws GDI text on the sent surface with the desired color and position in either 8- or 16-bit color modes. The function is overloaded to take both a COLORREF in the form of the RGB() macro or a 256 (8-bit) color index. Note that the destination surface must be unlocked for the function to operate, because it locks it momentarily to perform the text blitting with GDI. Returns TRUE if successful.

Example:

// draw text with color RGB(100,100,0);
Draw_Text_GDI("This is a test",100,50,
    RGB16Bit(100,100,0),lpddsprimary);

Math and Error Functions

We will rewrite the math library quite a bit, but we might as well take a look at what there is to offer, because I might use it in some of the early demos. Additionally, there is some error support that you can use to write errors or diagnostic text as the engine runs. This is helpful, because debugging DirectX applications is so hard because of their resource sharing, or lack thereof <GRIN>.

Function Prototype:

int Fast_Distance_2D(int x, int y);

Purpose:

Fast_Distance() computes the distance from (0,0) to (x,y) using a fast approximation. Returns the distance within a 3.5% error truncated to an integer. It uses a Taylor series approximation, if you're interested.

Example:

int x1=100,y1=200; // object one
int x2=400,y2=150; // object two

// compute the distance between object one and two
int dist = Fast_Distance_2D(x1-x2, y1-y2);

Function Prototype:

float Fast_Distance_3D(float x, float y, float z);

Purpose:

Fast_Distance_3D() computes the distance from (0,0,0) to (x,y,z) using a fast approximation. The function returns the distance within an 11% margin of error.

Example:

// compute the distance from (0,0,0) to (100,200,300)
float dist = Fast_Distance_3D(100,200,300);

Function Prototype:

int Find_Bounding_Box_Poly2D(
    POLYGON2D_PTR poly, // the polygon
    float &min_x, float &max_x, // bounding box
    float &min_y, float &max_y);

Purpose:

Find_Bounding_Box_Poly2D() computes the smallest rectangle that contains the sent polygon in poly. Returns TRUE if successful. Also, notice the function takes parameters by reference.

Example:

POLYGON2D poly; // assume this is initialized
int min_x, max_x, min_y, max_y; // hold result

// find bounding box
Find_Bounding_Box_Poly2D(&poly,min_x,max_x,min_y,max_y);

Function Prototype:

int Open_Error_File(char *filename);

Purpose:

Open_Error_File() opens a disk file that receives error messages sent by you via the Write_Error() function. Returns TRUE if successful.

Example:

// open a general error log
Open_Error_File("errors.log");

Function Prototype:

int Close_Error_File(void);

Purpose:

Close_Error_File() closes a previously opened error file. Basically, it shuts down the stream. If you call this and an error file is not open, nothing will happen. Returns TRUE if successful.

Example:

// close the error system, note no parameter needed
Close_Error_File();

Function Prototype:

int Write_Error(char *string, ...); // error formatting string

Purpose:

Write_Error() writes an error out to the previously opened error file. If there is no file open, the function returns a FALSE and there is no harm. Note that the function uses the (...) variable parameter indicator, so you can use this function as you would printf(). Returns TRUE if successful.

Example:

// write out some stuff
Write_Error("\nSystem Starting...");
Write_Error("\nx-vel = %d", y-vel = %d", xvel, yvel);

Bitmap Functions

The following function set makes up the BITMAP_IMAGE and BITMAP_FILE manipulation routines. There are functions to load 8-, 16-, 24-, 32-bit bitmaps as well as to extract images from them and create simple BITMAP_IMAGE objects (which are not DirectDraw surfaces). In addition, functionality exists to draw these images in 8- and 16-bit modes only, but there is no clipping support. Hence, you can modify the source yourself if you need clipping support, or step up to the BOB (Blitter Object), which is described at the end of the section. We will probably use these functions (or variants) to help load texture and light maps for our 3D rendering, so it's a good idea to take a close look at them.

Function Prototype:

int Load_Bitmap_File(BITMAP_FILE_PTR bitmap, // bitmap file
      char *filename); // disk .BMP file to load

Purpose:

Load_Bitmap_File() loads a .BMP format bitmap file off disk into the sent BITMAP_FILE structure, where you can manipulate it as you will. The function loads 8-, 16-, and 24-bit bitmaps, as well as the palette information on 8-bit .BMP files. Returns TRUE if successful.

Example:

// let's load "andre.bmp" off disk
BITMAP_FILE bitmap_file;

Load_Bitmap_File(&bitmap_file, "andre.bmp");

Function Prototype:

int Unload_Bitmap_File(BITMAP_FILE_PTR bitmap); // bitmap to close and unload

Purpose:

Unload_Bitmap_File() deallocates the memory associated with the image buffer of a loaded BITMAP_FILE. Call this function when you have copied the image bits and/or are done working with a particular bitmap. You can reuse the structure, but the memory must be freed first. Returns TRUE if successful.

Example:

// close the file we just opened
Unload_Bitmap_File(&bitmap_file);

Function Prototype:

int Create_Bitmap(BITMAP_IMAGE_PTR image, // bitmap image
     int x, int y, // starting position
     int width, int height, // size of bitmap
     int bpp=8); // the bit depth of bitmap

Purpose:

Create_Bitmap() creates an 8- or 16-bit system memory bitmap at the given position with the given size. The bitmap is initially blank and stored in the BITMAP_IMAGE image. The bitmap is not a DirectDraw surface, so there is no acceleration or clipping available. The function defaults to 8-bit bitmaps unless you override the bpp parameter with 16 for 16-bit. Returns TRUE if successful.

NOTE

There is a big difference between a BITMAP_FILE and a BITMAP_IMAGE. A BITMAP_FILE is a disk .BMP file, whereas a BITMAP_IMAGE is a system memory object like a sprite that can be moved and drawn.

Examples:

// let's create a 64x64 8-bit bitmap image at (0,0)
BITMAP_IMAGE ship;
Create_Bitmap(&ship, 0,0, 64,64);

// and now a 16-bit 32x32 image at (100,100)
BITMAP_IMAGE ship2;

// note the addition of 16 to override the default 8-bit depth
Create_Bitmap(&ship, 0,0, 32,32,16);

Function Prototype:

int Destroy_Bitmap(BITMAP_IMAGE_PTR image); // bitmap image to destroy

Purpose:

Destroy_Bitmap() is used to release the memory allocated during the creation of a BITMAP_IMAGE object (either 8- or 16-bit). You should call this function on your object when you're all done working with it—usually during the shutdown of the game, or if it's been destroyed in a bloody battle. Returns TRUE if successful.

Example:

// destroy the previously created BITMAP_IMAGE
Destroy_Bitmap(&ship);

Function Prototype:

int Load_Image_Bitmap(
 BITMAP_IMAGE_PTR image, // bitmap to store image in
 BITMAP_FILE_PTR bitmap, // bitmap file object to load from
 int cx,int cy, // coordinates where to scan (cell or abs)
 int mode); // image scan mode: cell based or absolute

// 16-bit version
int Load_Image_Bitmap16(
 BITMAP_IMAGE_PTR image, // bitmap to store image in
 BITMAP_FILE_PTR bitmap, // bitmap file object to load from
 int cx,int cy, // coordinates where to scan (cell or abs)
 int mode); // image scan mode: cell based or absolute


#define BITMAP_EXTRACT_MODE_CELL 0
#define BITMAP_EXTRACT_MODE_ABS 1

Purpose:

Load_Image_Bitmap*() is used to scan an image from a previously loaded BITMAP_FILE object into the sent BITMAP_IMAGE storage area—this is how you actually get objects and image bits into a BITMAP_IMAGE. Of course, the bitmap file and the bitmap object must be the same bit depth (8- or 16-bit), and you have to use the appropriate version of the function.

To use the function, you first must have a BITMAP_FILE loaded and have created the BITMAP_IMAGE. Then you make the call to scan an image of the same size out of the bitmap data stored in the BITMAP_FILE. There are two ways the function works: cell mode or absolute mode.

In cell mode (BITMAP_EXTRACT_MODE_CELL), the image is scanned, making the assumption that all the images are in the .BMP file in a template that is some given size mxn, with a 1 pixel-thick border between each cell, which usually range from 8x8, 16x16, 32x32, 64x64, and so on. Take a look at TEMPLATE*.BMP on the CD from this chapter—it contains a number of templates. Cell numbers range from left to right, top to bottom, and start with (0,0).

The second mode of operation is absolute coordinate mode (BITMAP_EXTRACT_MODE_ABS). In this mode, the image is scanned at the exact coordinates, sent in cx, cy. This method is good if you want to load your artwork with various sized images on the same .BMP. Hence, you can't template them.

Example:

8-bit example
// assume the source bitmap .BMP file is 640x480x8 and
// has a 8x8 matrix of cells that are each 32x32
// then to load the 3rd cell to the right on the 2nd
// row (cell 2,1), you would do this

// load in the .BMP file into memory
BITMAP_FILE bitmap_file;
Load_Bitmap_File(&bitmap_file,"images.bmp");


// initialize the bitmap
BITMAP_IMAGE ship;
Create_Bitmap(&ship, 0,0, 32,32);

// now scan out the data
Load_Image_Bitmap(&ship, &bitmap_file, 2,1,
     BITMAP_EXTRACT_MODE_CELL);

// the exact same example in 16-bit mode
// assume the source bitmap .BMP file is 640x480x16 and
// has a 8x8 matrix of cells that are each 32x32
// then to load the 3rd cell to the right on the 2nd
// row (cell 2,1), you would do this

// load in the .BMP file into memory
BITMAP_FILE bitmap_file;
Load_Bitmap_File(&bitmap_file,"images.bmp");


// initialize the bitmap
BITMAP_IMAGE ship2;
Create_Bitmap(&ship2, 0,0, 32,32,16);
************* 0,0,31,31,16? it's ok:al

// now scan out the data
Load_Image_Bitmap16(&ship2, &bitmap_file, 2,1,
     BITMAP_EXTRACT_MODE_CELL);

To load the exact same image assuming it's still in the template, but using the absolute mode, we have to figure out the coordinates—remember, there's a 1 pixel-thick partitioning wall on each side of the image.

// 8-bit example
Load_Image_Bitmap(&ship, &bitmap_file,
     2*(32+1)+1,1*(32+1)+1,
     BITMAP_EXTRACT_MODE_ABS);

Function Prototype:

int Draw_Bitmap(BITMAP_IMAGE_PTR source_bitmap, // bitmap to draw
    UCHAR *dest_buffer, // video buffer
    int lpitch, // memory pitch
    int transparent); // transparency?

// 16-bit version
int Draw_Bitmap16(BITMAP_IMAGE_PTR source_bitmap, // bitmap to draw
    UCHAR *dest_buffer, // video buffer
    int lpitch, // memory pitch
    int transparent); // transparency?

Purpose:

Draw_Bitmap*() draws the sent bitmap on the destination memory surface in either 8- or 16-bit mode with or without transparency. If transparent is 1, transparency is enabled and any pixel that is color index 0 or RGB word 0.0.0 in 16-bit mode will not be copied. Function returns TRUE if successful.

Example:

// draw our little ship on the back buffer
// in 8-bit mode
Draw_Bitmap( &ship, back_buffer, back_lpitch, 1);

// and now in 16-bit mode
Draw_Bitmap16( &ship2, back_buffer, back_lpitch, 1);

Function Prototype:

int Flip_Bitmap(UCHAR *image, // image bits to vertically flip
    int bytes_per_line, // bytes per line
    int height); // total rows or height

Purpose:

Flip_Bitmap() is usually called internally to flip upside down .BMP files during loading, but you might want to use it to flip an image yourself (8- or 16-bit). The function does an in-memory flip, and actually inverts the bitmap line by line, and your original sent data will be inverted, so watch out! Returns TRUE if successful.

Example:

// for fun flip the image bits of our little ship
// assuming 1 byte per pixel in 8 bit mode
Flip_Bitmap(ship->buffer, ship->width, ship->height);

// and here's the call for 16-bit mode, same call
// but we need to double the width since there are
// 2 bytes per pixel
Flip_Bitmap(ship2->buffer, 2*ship2->width, ship2->height);

Function Prototype:

int Copy_Bitmap(BITMAP_IMAGE_PTR dest_bitmap,
    int dest_x, int dest_y,
    BITMAP_IMAGE_PTR source_bitmap,
    int source_x, int source_y,
    int width, int height);

Purpose:

Copy_Bitmap() copies a rectangular region from one bitmap to another. The source and destination can be the same bitmap; however, the regions must not overlap—otherwise, undefined results might occur. Returns TRUE if successful.

Example:

// load in the .BMP file into memory
BITMAP_FILE bitmap_file;
Load_Bitmap_File(&bitmap_file,"playfield.bmp");


// initialize a bitmap to hold playfield
BITMAP_IMAGE playfield;
Create_Bitmap(&playfield, 0,0, 400,400,16);

// now scan out the data
Load_Image_Bitmap16(&playfield, &bitmap_file, 0,0,
     BITMAP_EXTRACT_MODE_ABSOLUTE);


// now copy the top portion of the same bitmap to the bottom
// really chunky scrolling!
Copy_Bitmap(&playfield, 0,200,
   &playfield, 0,0,
   200,200);

Function Prototype:

int Scroll_Bitmap(BITMAP_IMAGE_PTR image, int dx, int dy=0);

Purpose:

Scroll_Bitmap() scrolls the sent bitmap horizontally and/or vertically an amount dx,dy in the x- and y-axes, respectively. Positive numbers scroll right and down; negative numbers scroll left and up. Returns TRUE if successful.

Example:

// scroll the playfield 2 pixels to the right
Scroll_Bitmap(&playfield, 2,0);

8-Bit Palette Functions

The following functions make up the 8-bit 256 color palette interface. These functions are only relevant if you have the display set for a 256-color mode (8-bit color). However, in windowed display modes, the first and last 10 colors are used by Windows, and you can't alter them. (Of course, in full-screen mode you can.)

Additionally, the basic data structure used to hold a color is a native Win32 structure called PALETTEENTRY:

typedef struct tagPALETTEENTRY { // pe
 BYTE peRed; // red channel 8-bits
 BYTE peGreen; // green channel 8-bits
 BYTE peBlue; // blue channel 8-bits
 BYTE peFlags; // flags control, PC_EXPLICIT for windows colors
     // PC_NOCOLLAPSE for all others
} PALETTEENTRY;

With that in mind, let's look at the palette functions.

Function Prototype:

int Set_Palette_Entry(
    int color_index, // color index to change
    LPPALETTEENTRY color); // the color

Purpose:

Set_Palette_Entry() is used to change a single color in the color palette. You simply send the color index 0..255 along with a pointer to PALETTEENTRY holding the color, and the update will occur on the next frame. In addition, this function updates the shadow palette. Note that this function is slow, so if you need to update the entire palette, use Set_Palette(). Returns TRUE if successful, FALSE otherwise.

Example:

// set color 0 to black
PALETTEENTRY black = {0,0,0,PC_NOCOLLAPSE};
Set_Palette_Entry(0,&black);

Function Prototype:

int Get_Palette_Entry(
    int color_index, // color index to retrieve
    LPPALETTEENTRY color); // storage for color

Purpose:

Get_Palette_Entry() retrieves a palette entry from the current palette. However, the function is very fast because it retrieves the data from the RAM-based shadow palette. Hence, you can call this as much as you like, and it doesn't disturb the hardware at all. However, if you make changes to the system palette with the use of Set_Palette_Entry() or Set_Palette(), the shadow palette will not be updated and the data retrieved might not be valid. Returns TRUE if successful, FALSE otherwise.

Example:

// let's get palette entry 100
PALETTEENTRY color;
Get_Palette_Entry(100,&color);

Function Prototype:

int Save_Palette_To_File(
    char *filename, // filename to save at
    LPPALETTEENTRY palette); // palette to save

Purpose:

Save_Palette_To_File() saves the sent palette data to an ASCII file on disk for later retrieval or processing. This function is very handy if you generate a palette on the fly and want to store it on disk. However, the function assumes that the pointer in the palette points to a 256 entry palette—so watch out! Returns TRUE if successful, FALSE otherwise.

Example:

PALETTEENTRY my_palette[256]; // assume this is built

// save the palette we made
// note file name can be anything, but I like *.pal
Save_Palette_To_File("/palettes/custom1.pal",&my_palette);

Function Prototype:

int Load_Palette_From_File(
   char *filename, // file to load from
   LPPALETTEENTRY palette); // storage for palette

Purpose:

Load_Palette_From_File() is used to load a previously saved 256-color palette from disk via Save_Palette_To_File(). You simply send the file name along with storage for all 256 entries and the palette is loaded off disk into the data structure. However, the function does NOT load the entries into the hardware palette; you must do this yourself with Set_Palette(). Returns TRUE if successful, FALSE otherwise.

Example:

// load the previously saved palette
Load_Palette_From_File("/palettes/custom1.pal",&my_palette);

NOTE

When you create a 256-color display mode via a call to DDRAW_INIT(), the function will load a standard palette that has almost optimal color space coverage. The name of the palette file is PALDATA2.DAT. It's nothing more than an ASCII file with 256 line entries, each with a red, green, and blue value in the range of 0..255.

Function Prototype:

int Set_Palette(LPPALETTEENTRY set_palette); // palette to load into hardware

Purpose:

Set_Palette() loads the sent palette data into the hardware and updates the shadow palette, also. Returns TRUE if successful, FALSE otherwise.

Example:

// lets load the palette into the hardware
Set_Palette(&my_palette);

Function Prototype:

int Save_Palette(LPPALETTEENTRY sav_palette); // storage for palette

Purpose:

Save_Palette() scans the hardware palette out into sav_palette so that you can save it to disk or manipulate it. sav_palette must have enough storage for all 256 entries.

Example:

// retrieve the current DirectDraw hardware palette
PALETTEENTRY hardware_palette[256];
Save_Palette(&hardware_palette);

Function Prototype:

int Rotate_Colors(int start_index, // starting index 0..255
     int end_index); // ending index 0..255

Purpose:

Rotate_Colors() rotates a bank of colors in a cyclic manner. It manipulates the color palette hardware directly. Returns TRUE if successful, FALSE otherwise.

Example:

// rotate the entire palette
Rotate_Colors(0,255);

Function Prototype:

int Blink_Colors(int command, // blinker engine command
     BLINKER_PTR new_light, // blinker data
     int id); // id of blinker

Purpose:

Blink_Colors() is used to create asynchronous palette animation. The function is too long to explain here, but please refer to the previous chapter for a more in-depth description.

Example:

N/A

Utility Functions

Function Prototype:

DWORD Get_Clock(void);

Purpose:

Get_Clock() returns the current clock time in milliseconds since Windows was started.

Example:

// get the current tick count
DWORD start_time = Get_Clock();

Function Prototype:

DWORD Start_Clock(void);

Purpose:

Start_Clock() basically makes a call to Get_Clock() and stores the time in a global variable for you. Then you can call Wait_Clock(), which will wait for some number of milliseconds since your call to Start_Clock(). Returns the starting clock value at the time of the call.

Example:

// start the clock and set the global
Start_Clock();

Function Prototype:

DWORD Wait_Clock(DWORD count); // number of milliseconds to wait

Purpose:

Wait_Clock() simply waits the sent number of milliseconds since the call was made to Start_Clock(). Returns the current clock count at the time of call. However, the function will NOT return until the time difference has elapsed.

Example:

// wait 30 milliseconds
Start_Clock();

// code...

Wait_Clock(30);

NOTE

Later in the book, we will probably upgrade to using the high performance timers that Windows supports for better resolution.

Function Prototype:

int Collision_Test(int x1, int y1, // upper lhs of obj1
     int w1, int h1, // width, height of obj1
     int x2, int y2, // upper lhs of obj2
     int w2, int h2);// width, height of obj2

Purpose:

Collision_Test() basically performs an overlapping rectangle test on the two sent rectangles. The rectangles can represent whatever you like. You must send the upper-left hand corner coordinates of each rectangle, along with its width and height. Returns TRUE if there is an overlap, FALSE otherwise.

Example:

// do these two BITMAP_IMAGE's overlap?
if (Collision_Test(ship1->x,ship1->y,ship1->width,ship1->height,
     ship2->x,ship2->y,ship2->width,ship2->height))
 { // hit

 } // end if

Function Prototype:

int Color_Scan(int x1, int y1, // upper left of rect
    int x2, int y2, // lower right of rect
    UCHAR scan_start, // starting scan color
    UCHAR scan_end, // ending scan color
    UCHAR *scan_buffer, // memory to scan
    int scan_lpitch); // linear memory pitch

Purpose:

Color_Scan() is another collision detection algorithm that scans a rectangle for a single 8-bit value or sequence of values in some continuous range. You can use it to determine whether a color index is present within some area. Of course, it only works with 8-bit images, but the source is easily extensible to 16-bit or higher modes. Returns TRUE if the color(s) is found.

Example:

// scan for colors in range from 122-124 inclusive
Color_Scan(10,10, 50, 50, 122,124, back_buffer, back_lpitch);

The BOB (Blitter Object) Engine

Although with a bit of programming you can get the BITMAP_IMAGE type to do what you want, it is lacking in a serious way—it doesn't use DirectDraw surfaces, and hence there is no support for acceleration. Therefore, I have created a type called a BOB, or blitter object, that is very similar to a sprite. A sprite (for those of you that have been in a game programming cave) is nothing more than an object you can move around the screen that usually doesn't disturb the background. In our case, this isn't true; therefore, I called my animation object a BOB rather than a sprite, so there!

We will use the BOB engine very little in this book, but I still wanted you to see it, because it's a good example of using DirectDraw surfaces and full 2D acceleration. So let's talk briefly about what a BOB is. First here's the data structure for a BOB:

// the blitter object structure BOB
typedef struct BOB_TYP
  {
  int state;   // the state of the object (general)
  int anim_state;  // an animation state variable, up to you
  int attr;   // attributes pertaining to the object (general)
  float x,y;   // position bitmap will be displayed at
  float xv,yv;   // velocity of object
  int width, height; // the width and height of the bob
  int width_fill;  // internal, used to force 8*x wide surfaces
  int bpp;   // bits per pixel needed for 8/16 bit support
  int counter_1;  // general counters
  int counter_2;
  int max_count_1; // general threshold values;
  int max_count_2;
  int varsI[16];  // stack of 16 integers
  float varsF[16]; // stack of 16 floats
  int curr_frame;  // current animation frame
  int num_frames;  // total number of animation frames
  int curr_animation; // index of current animation
  int anim_counter; // used to time animation transitions
  int anim_index;  // animation element index
  int anim_count_max; // number of cycles before animation
  int *animations[MAX_BOB_ANIMATIONS]; // animation sequences
  // the bitmap images DD surfaces
  LPDIRECTDRAWSURFACE7 images[MAX_BOB_FRAMES]; 
  } BOB, *BOB_PTR;

A BOB is basically a graphical object represented by one or more DirectDraw surfaces (up to 64 as currently #defined). You can move a BOB, draw a BOB, animate a BOB, and set it in motion. BOBs consider the current DirectDraw clipper, so they are clipped as well as accelerated—which is a good thing! Figure 3.10 shows a BOB and it relationship to its animation frames.

Figure 3.10Figure 3.10 The BOB animation system.

Also, the BOB engine supports 8- or 16-bit images and animation sequences, so you can load in a set of frames and an animation sequence, and the sequence will play feeding from the frames. This is a very cool feature. Additionally, BOBs work with both 8- and 16-bit color, but most of the functions have internal logic that know what bit depth the system is in, so you don't have to worry it. The only functions that you need to explicitly call a different version for 16-bit mode are Load_Frame_BOB*() (when you load) and Draw_BOB*() (when you draw a BOB), but we will get to that, along with reviewing examples of both 8- and 16-bit demos, at the end of the chapter.

Finally, all the BOB functions return TRUE if successful and FALSE otherwise. So let's take a look at them all....

Function Prototype:

int Create_BOB(BOB_PTR bob, // ptr to bob to create
 int x, int y,   // initial position of bob
 int width, int height, // size of bob
 int num_frames, // total number of frames for bob
 int attr,  // attributes of bob
 int mem_flags=0, // surface memory flags, 0 is VRAM
 USHORT color_key_value=0, // color key value is interpreted as
        // 8-bit index, or 16-bit RGB based
        // on the bpp setting below
 int bpp=8); // bits per pixel, default is 8

Purpose:

Create_BOB() creates a single BOB object and sets it all up. The function assigns all the internal variables, in addition to creating a separate DirectDraw surface for each frame. Most of the parameters are self-explanatory—the only value that needs a little explanation is the attribute variable attr. Take a look at Table 3.1 to see a better description of each of the attributes you can logically OR together and send in this field.

Table 3.1 Valid BOB Attributes

Value

Meaning

BOB_ATTR_SINGLE_FRAME

Create a BOB with a single frame.

BOB_ATTR_MULTI_FRAME

Create a BOB with multiple frames, but the animation of the BOB will be a linear sequence through the frames 0..n.

BOB_ATTR_MULTI_ANIM

Create a multiple frame BOB that supports animation sequences.

BOB_ATTR_ANIM_ONE_SHOT

If this is set then when an animation sequence plays, the animation sequence will only play once and then stop. At this point, the internal variable anim_state will be set. To play the animation again, reset this variable.

BOB_ATTR_BOUNCE

This flag tells the BOB to bounce off the screen boundaries like a ball. This only works if you use Move_BOB().

BOB_ATTR_WRAPAROUND

This flag tells the BOB to wrap around to the other side of the screen as it moves. This only works if you use Move_BOB().


Example:

Here are some examples creating BOBs. First, a single frame 8-bit BOB at (50,100) with a size of 96x64:

BOB car; // a car bob

// create the bob
if (!Create_BOB(&car, 50,100, // address of bob and initial position
    96,64,  // size of bob
    1,   // number of frames
    BOB_ATTR_SINGLE_FRAME, // attribute
    0, // memory flags
    0, // color key, for 8-bit this is the index
    8)) // bits per pixel, 8 for 256 color modes
 { /* error */ }

And here's a multiple frame 16-bit BOB with 8 frames and a size of 32x32:

BOB ship; // a space ship bob

// create the bob
if (!Create_BOB(&ship, 0,0, // address of bob and initial position
    32,32, // size of bob
    8,  // number of frames
    BOB_ATTR_MULTI_FRAME, // attribute
    0, // memory flags
    0, // color key, for 16-bit mode this is the RGB word
    16)) // bits per pixel, 16 for 16 bit modes
 { /* error */ }

Finally, a multiple frame 8-bit BOB that supports animation sequences:

BOB greeny; // a little green man bob

// create the bob
if (!Create_BOB(&greeny, 0,0,
 32,32,32,BOB_ATTR_MULTI_ANIM,0,0,8))
 { /* error */ }

TIP

Note that the last three parameters to Create_BOB() all have default values. Therefore, you don't need to override them if you don't want to. If 0,0,8 is okay with you, you don't have to type them.

Function Prototype:

int Destroy_BOB(BOB_PTR bob); // ptr to bob to destroy

Purpose:

Destroy_BOB() destroys a previously created BOB. It doesn't matter if it's 8- or 16-bit.

Example:

// destroy the BOB above, you would do this
Destroy_BOB(&greeny);

Function Prototype:

int Draw_BOB(BOB_PTR bob, // ptr of bob to draw
 LPDIRECTDRAWSURFACE7 dest); // dest surface to draw on

// and the 16-bit version
int Draw_BOB16(BOB_PTR bob, // ptr of bob to draw
 LPDIRECTDRAWSURFACE7 dest); // dest surface to draw on

Purpose:

Draw_BOB*() is a very powerful function. It draws the sent BOB on the DirectDraw surface you send it. The BOB is drawn in its current position and current frame (as defined by its animation parameters). Also, make sure to use the correct version of the function depending on whether your BOB is 8- or 16-bit; otherwise, you might get half a BOB!

CAUTION

For this function to work, the destination surface must NOT be locked.

Example:

// this is how you would position a multiframe BOB at
// (50,50) and draw the first frame of it on the back
// surface:
BOB ship; // a space ship bob

// create the bob, 8-bit default
if (!Create_BOB(&ship, 0,0,
    32,32,8,BOB_ATTR_MULTI_FRAME,0))

// load the bob images in..we'll get to this in a bit
// set the position and frame of bob
ship.x = 50;
ship.y = 50;
ship.curr_frame = 0; // this contains the frame to draw

// draw bob
Draw_BOB(&ship, lpddsback);

Function Prototype:

int Draw_Scaled_BOB(BOB_PTR bob, // ptr of bob to draw
 int swidth, int sheight, // new width and height of bob
 LPDIRECTDRAWSURFACE7 dest); // dest surface to draw on

// and the 16-bit version
int Draw_Scaled_BOB16(BOB_PTR bob, // ptr of bob to draw
 int swidth, int sheight, // new width and height of bob
 LPDIRECTDRAWSURFACE7 dest); // dest surface to draw on

Purpose:

Draw_Scaled_BOB*() works exactly like Draw_BOB(), except you can send any width and height to draw the BOB and the BOB will be scaled appropriately. This is very cool, and if you have acceleration, it's a great way to scale a BOB to make it look 3D! In fact, we will use a technique like this when we create billboards for our 3D applications.

NOTE

Billboards are simply 2D images that are drawn in 3D, but are always perpendicular to the view direction. For example, in Doom, all the creatures are billboard sprites, while the world is polygon-based.

Example:

// an example of drawing the ship 128x128 even though
// it was created as only 32x32 pixels
// 8-bit version
Draw_Scaled_BOB(&ship, 128,128,lpddsback);

Function Prototype:

int Load_Frame_BOB(
 BOB_PTR bob, // ptr of bob to load frame into
 BITMAP_FILE_PTR bitmap,// ptr of bitmap file to scan data
 int frame, // frame number to place image into 0,1,2...
 int cx,int cy, // cell pos or abs pos to scan from
 int mode);  // scan mode, same as Load_Frame_Bitmap()

// and the 16-bit version
int Load_Frame_BOB16(
 BOB_PTR bob, // ptr of bob to load frame into
 BITMAP_FILE_PTR bitmap,// ptr of bitmap file to scan data
 int frame, // frame number to place image into 0,1,2...
 int cx,int cy, // cell pos or abs pos to scan from
 int mode);  // scan mode, same as Load_Frame_Bitmap()

Purpose:

The Load_Frame_BOB*() function works identically to the Load_Frame_Bitmap() function, so refer to it for details. The only addition is the control of the frame to load via frame. If you create a BOB that has four frames, you will load the frames one by one. Additionally, you must call the correct version of the function (8- or 16-bit) based on the bit depth of the data and screen.

Example:

// here's an example of loading 4 frames into a 16-bit BOB from a
// bitmap file in cell mode

BOB ship; // the bob
// loads frames 0,1,2,3 from cell position (0,0), (1,0),
// (2,0), (3,0)
// from bitmap16bit bitmap file, assume it has been loaded

for (int index=0; index<4; index++)
  Load_Frame_BOB16(&ship,&bitmap16bit,
      index, index,0,
      BITMAP_EXTRACT_MODE_CELL );

NOTE

The next set of BOB manipulation functions work on the data structure elements of the BOB, so they work identically on both 8- and 16-bit BOBs.

Function Prototype:

int Load_Animation_BOB(
 BOB_PTR bob, // bob to load animation into
 int anim_index, // which animation to load 0..15
 int num_frames, // number of frames of animation
 int *sequence); // ptr to array holding sequence

Purpose:

Load_Animation() takes a little explaining. The function is used to load one of 16 arrays internal to the BOB that contain animation sequences. Each sequence contains an array of indices or frame numbers to display in sequence. This is shown at the bottom of Figure 3.10.

Example:

You might have a BOB that has eight frames 0,1,...7, but you might have four animations defined as follows:

int anim_walk[] = {0,1,2,1,0};
int anim_fire[] = {5,6,0};
int anim_die[] = {3,4};
int anim_sleep[] = {0,0,7,0,0};

Then to load the animations into the BOB, you would do this:

// create a mutli animation bob
// create the bob
if (!Create_BOB(&alien, 0,0, 32,32,8,BOB_ATTR_MULTI_ANIM,0))
 { /* error */ }

// load the bob frames in...
// load walk into animation 0
Load_Animation_BOB(&alien, 0,5,anim_walk);

// load fire into animation 1
Load_Animation_BOB(&alien, 1,3,anim_fire);

// load die into animation 2
Load_Animation_BOB(&alien, 2,2,anim_die);

// load sleep into animation 3
Load_Animation_BOB(&alien, 3,5,anim_sleep);

After loading the animations, you can set the active animation and play them with functions you'll see in a minute.

Function Prototype:

int Set_Pos_BOB(BOB_PTR bob, // ptr to bob to set position
    int x, int y); // new position of bob

Purpose:

Set_Pos_BOB() is a simple way to set the position of the BOB. This does nothing more than assign the internal (x,y) variables, but it's nice to have a function.

Example:

// set the position of the alien BOB above
Set_Pos_BOB(&alien, player_x, player_y);

Function Prototype:

int Set_Vel_BOB(BOB_PTR bob, // ptr to bob to set velocity
   int xv, int yv); // new x,y velocity

Purpose:

Each BOB has an internal velocity contained in (xv,yv). Set_Vel_BOB() simply assigns these values the new values sent in the function. The velocity values in the BOB won't do anything unless you use the function Move_BOB() to move your BOBs. However, even if you don't, you can use xv,yv to track the velocity of the BOB yourself.

Example:

// make the BOB move in a straight horizontal line
Set_Vel_BOB(&alien, 10,0);

Function Prototype:

int Set_Anim_Speed_BOB(BOB_PTR bob, // ptr to bob
     int speed); // speed of animation

Purpose:

Set_Anim_Speed() sets the internal animation rate for a BOB anim_count_max. The higher this number is, the slower the animation; the lower the number (0 is the lowest), the faster the animation. However, this function only matters if you use the internal BOB animation function Animate_BOB(). And of course, you must have created a BOB that has multiple frames.

Example:

// set the rate to change frames every 30 frames
Set_Anim_Speed_BOB(&alien, 30);

Function Prototype:

int Set_Animation_BOB(
   BOB_PTR bob, // ptr of bob to set animation
   int anim_index); // index of animation to set

Purpose:

Set_Animation_BOB() sets the current animation that will be played by the BOB. In the earlier example of Load_Animation_BOB(), we created four animations.

Example:

// make animation sequence number 2 active
Set_Animation_BOB(&alien, 2);

NOTE

This also resets the BOB animation to the first frame in the sequence.

Function Prototype:

int Animate_BOB(BOB_PTR bob); // ptr to bob to animate

Purpose:

Animate_BOB() animates a BOB for you. Normally, you would call this function once a frame to update the animation of the BOB.

Example:

// erase everything...
// move everything...
// animate everything
Animate_BOB(&alien);

Function Prototype:

int Move_BOB(BOB_PTR bob); // ptr of bob to move

Purpose:

Move_BOB() moves the BOB a delta of xv,yv (set with the Set_Vel_BOB() function), then, depending on the attributes, will either bounce the BOB off the walls, wrap it around, or do nothing. Similarly to the Animate_BOB() function, you would place this call once in the main loop right after (or before) Animate_BOB().

Example:

// animate bob
Animate_BOB(&alien);

// move it
Move_BOB(&alien);

Function Prototype:

int Hide_BOB(BOB_PTR bob); // ptr to bob to hide

Purpose:

Hide_BOB() simply sets the BOB's internal visibility flag to 0, after which Draw_BOB() won't display the BOB anymore.

Example:

// hide the bob
Hide_BOB(&alien);

Function Prototype:

int Show_BOB(BOB_PTR bob); // ptr to bob to show

Purpose:

Show_BOB() sets the visibility flag to 1 on a BOB, so it will be drawn (it undoes a Hide_BOB() call). Here's an example of hiding and showing a BOB because you are displaying a GDI object or something, and don't want the BOB to occlude it:

Example:

// Hide_BOB(&alien);
// make calls to Draw_BOB and GDI etc.
Show_BOB(&alien);

Function Prototype:

int Collision_BOBS(BOB_PTR bob1, // ptr to first bob
     BOB_PTR bob2); // ptr to second bob

Purpose:

Collision_BOBS() detects whether the bounding rectangles of two BOBs are overlapping. This can be used for collision detection in a game to see whether a player BOB hits a missile BOB or whatever.

Example:

// check if a missile BOB hit a player BOB:
if (Collision_BOBS(&missile, &player))
 { /* make explosion sound */ }

That's about it for the graphics module of the T3DLIB game library (T3DLIB1.CPP|H). Now let's move on to the sound module.

  • + Share This
  • 🔖 Save To Your Account