Open GL Super Bible

Previous Table of Contents Next


Automatically Generating Texture Coordinates

Generating all those texture coordinates can be tedious. Fortunately, OpenGL has an answer that we can use! In the current drawing code, we issue glTexCoord2i calls

glTexCoord2i(x * 2, y * 2);

for each and every point in the terrain. But instead of doing this for each point, we can use the glTexGen functions to define the S and T coordinates in terms of the X and Z position in the scene (Y is used for the height). To generate coordinates for our terrain, then, we can use the following:

static GLint s_vector[4] = { 2, 0, 0, 0 };
static GLint t_vector[4] = { 0, 0, 2, 0 };

glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
glTexGeniv(GL_S, GL_OBJECT_PLANE, s_vector);

glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
glTexGeniv(GL_T, GL_OBJECT_PLANE, t_vector);

Here the GL_OBJECT_LINEAR mapping mode maps the texture coordinates from object coordinates:

coordinate = X * vector[0] + Y * vector[1] +
             Z * vector[2] + W * vector[3]

The vector array is specified with glTexGen function:

void glTexGeniv(GLenum coord, GLenum pname, GLint *params)

where the coord parameter specifies which texture image coordinate to generate, GL_S or GL_T, and the pname parameter specifies the vector to define; in this case GL_OBJECT_PLANE. Finally, the params array specifies the object plane vector that is used to compute the texture coordinate.

The previous code for our terrain would generate these coordinates:

S = 2 * X
T = 2 * Z

To make OpenGL use these generated coordinates, you must enable texture coordinate generation, as follows:

glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);

The file TEXSCENE.C contains a version of our terrain viewing program that uses generated texture coordinates. The same techniques can be used with a 1D texture image. For the 1D image, you’d probably generate the S coordinate from the height (Y) to color the terrain based upon the height of the terrain. Generating texture coordinates is usually faster than specifying them manually in immediate mode, but is slower when using display lists.

Flying Through the Terrain

When the user is flying through the terrain, we need to regulate the flying speed based on the update rate of our scene. Rather than trying to maintain a fixed update rate—which can vary depending on the graphics card and CPU being used—we will measure the elapsed time from the last update to the current time. The FlyTerrain function manages this by measuring the time in milliseconds between each call, and moving the viewer forward at a fixed speed relative to the elapsed time.

Summary

In this chapter you’ve learned how to texture-map images onto polygons and other primitives using OpenGL. Texturing can provide that extra measure of realism that makes computer graphics so exciting to work with.

The OpenGL glTexParameter functions provide many ways to improve the quality of texture images when they are drawn. Mipmapped texture images provide multiple levels of detail that improve rendering quality and speed. Linear interpolation of texture images can improve certain types of textures, such as the sky texture used in the example project.

The glTexGen functions can simplify generation of texture coordinates by removing unnecessary or tedious calculations. By removing large amounts of conditional glTexCoord calls, automatic coordinate generation also simplifies programs that must display both textured and nontextured scenes

For games and other interactive, animated displays, you may want to support both textured and nontextured displays until accelerated OpenGL graphics boards become more widely available.

Now here is Listing 12-5, the complete terrain viewing program, TEXSCENE.C.

Listing 12-5 TEXSCENE.C: The terrain viewing program

#include 'texture.h’

#include 'texscene.h’

#include <stdarg.h>

#include <math.h>

#ifndef M_PI

#  define M_PI   (double)3.14159265358979323846

#endif /* !M_PI */



/*

 * Constants…

 */



#define TERRAIN_SIZE             21

#define TERRAIN_EDGE             ((TERRAIN_SIZE - 1) / 2)

#define TERRAIN_SCALE            (500.0 / TERRAIN_EDGE)



#define GRASS_HEIGHT             0.0

#define WATER_HEIGHT             0.0

#define TREES_HEIGHT             0.0

#define ROCKS_HEIGHT             0.5

#define MOUNTAINS_HEIGHT         1.0



/*

 * Globals…

 */ 



HWND            SceneWindow;           /* Scene window */

HPALETTE        ScenePalette;          /* Color palette (if necessary) */

HDC             SceneDC;               /* Drawing context */

HGLRC           SceneRC;               /* OpenGL rendering context */



GLuint          SkyTexture,            /* Sky texture image */

                GrassTexture,          /* Grass… */

                RocksTexture,          /* Rock… */

                WaterTexture,          /* Water… */

                TreesTexture,          /* Trees… */

                MountainsTexture;      /* Mountains… */



HBITMAP         GrassDownBitmap,       /* Grass button down image */

                GrassUpBitmap,         /* Grass button up image */

                GrassSelectBitmap,     /* Grass button selected image */

                RocksDownBitmap,       /* … */

                RocksUpBitmap,

                RocksSelectBitmap,

                WaterDownBitmap,

                WaterUpBitmap,

                WaterSelectBitmap,

                TreesDownBitmap,

                TreesUpBitmap,

                TreesSelectBitmap,

                MountainsDownBitmap,

                MountainsUpBitmap,

                MountainsSelectBitmap;



HWND            TerrainWindow;         /* Terrain dialog */

int             TerrainCurrent = IDC_WATER;

int             TerrainType[TERRAIN_SIZE][TERRAIN_SIZE];

GLfloat         TerrainHeight[TERRAIN_SIZE][TERRAIN_SIZE];

GLfloat         TerrainNormal[TERRAIN_SIZE][TERRAIN_SIZE][3];



double          MoveTime;              /* Last update time */

GLboolean       Moving = GL_FALSE,     /* GL_TRUE if flying */

                Drawing = GL_FALSE;    /* GL_TRUE if drawing */

POINT           CenterMouseXY;         /* Initial mouse pos */

GLfloat         Position[3] = { 0.0, TERRAIN_SCALE, 0.0 };

                                       /* Viewer position */

GLfloat         Heading = 0.0,         /* Viewer heading */

                Pitch = 0.0,           /* Viewer pitch */

                Roll = 0.0;            /* Viewer roll */



/*

 * Local functions…

 */



void                    DisplayErrorMessage(char *, …);

void                    MakePalette(int);

LRESULT CALLBACK        SceneProc(HWND, UINT, WPARAM, LPARAM);

UINT CALLBACK           TerrainDlgProc(HWND, UINT, WPARAM, LPARAM);

void                    InitializeTerrain(void);

void                    LoadAllTextures(void);

void                    LoadAllBitmaps(HINSTANCE);

void                    DrawTerrain(int, int);

void                    FlyTerrain(int, int);

void                    RepaintWindow(RECT *);

void                    SaveBitmapFile(void);

void                    PrintBitmap(void);

double                  GetClock(void);



/*

 * 'WinMain()’ - Main entry…

 */



int APIENTRY

WinMain(HINSTANCE hInst,               /* I - Current process instance */

        HINSTANCE hPrevInstance,       /* I - Parent process instance */

        LPSTR     lpCmdLine,           /* I - Command-line arguments */

        int       nCmdShow)            /* I - Show window at startup? */

{

  MSG           msg;                   /* Window UI event */

  WNDCLASS      wc;                    /* Window class */

  POINT         pos;                   /* Current mouse pos */



 /*

  * Initialize the terrain to all grasslands…

  */

  InitializeTerrain();



 /*

  * Register main window…

  */



  wc.style         = 0;

  wc.lpfnWndProc   = (WNDPROC)SceneProc;

  wc.cbClsExtra    = 0;

  wc.cbWndExtra    = 0;

  wc.hInstance     = hInst;

  wc.hIcon         = NULL;

  wc.hCursor       = LoadCursor(NULL, IDC_ARROW);

  wc.hbrBackground = 0;

  wc.lpszMenuName  = MAKEINTRESOURCE(IDR_MENU1);

  wc.lpszClassName = 'Textured Scene’;



  if (RegisterClass(&wc) == 0)

  {

    DisplayErrorMessage('Unable to register window class!’);

    return (FALSE);

  };



 /*

  * Then create it…

  */



  SceneWindow = CreateWindow('Textured Scene’, 'Textured Scene’,

                             WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN |

                             WS_CLIPSIBLINGS,

                             32, 32, 400, 300,

                             NULL, NULL, hInst, NULL);



  if (SceneWindow == NULL)

  {

    DisplayErrorMessage('Unable to create window!’);

    return (FALSE);

  };



  ShowWindow(SceneWindow, nCmdShow);

  UpdateWindow(SceneWindow);



 /*

  * Load the bitmaps for the buttons, and then create the terrain

  * editing dialog.

  */



  LoadAllBitmaps(hInst);



  TerrainWindow = CreateDialog(hInst,  MAKEINTRESOURCE(IDD_TERRAIN_DIALOG),

                               SceneWindow, (DLGPROC)TerrainDlgProc);



 /*

  * Loop on events until the user quits this application…

  */



  while (TRUE)

  {

   /*

    * Process all messages in the queue…

    */



    while (!Moving ||

           PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE) == TRUE)

      if (GetMessage(&msg, NULL, 0, 0))

      {

        TranslateMessage(&msg);

        DispatchMessage(&msg);

      }

      else

        return (1);



   /*

    * Handle flying as necessary…

    */



    GetCursorPos(&pos);

    FlyTerrain(pos.x, pos.y);

  };



  return (msg.wParam);

}



/*

 * 'DisplayErrorMessage()’ - Display an error message dialog.

 */



void

DisplayErrorMessage(char *format,           /* I - printf() style

                                            format string */

                    ...)                    /* I - Other arguments

                                            as necessary */

{

  va_list       ap;                         /* Argument pointer */

  char          s[1024];                    /* Output string */



  if (format == NULL)

    return;



  va_start(ap, format);

  vsprintf(s, format, ap);

  va_end(ap);



  MessageBeep(MB_ICONEXCLAMATION);

  MessageBox(NULL, s, 'Error’, MB_OK | MB_ICONEXCLAMATION);

}



/*

 * 'MakePalette()’ - Make a color palette for RGB colors if necessary.

 */



void

MakePalette(int pf)                         /* I - Pixel format ID */

{

  PIXELFORMATDESCRIPTOR pfd;                /* Pixel format information */

  LOGPALETTE            *pPal;              /* Pointer to logical

                                            palette */

  int                   nColors;            /* Number of entries

                                            in palette */

  int                   i,                  /* Color index */

                        rmax,               /* Maximum red value */

                        gmax,               /* Maximum green va6lue */

                        bmax;               /* Maximum blue value */



 /*

  * Find out if we need to define a color palette…

  */



  DescribePixelFormat(SceneDC, pf, sizeof(PIXELFORMATDESCRIPTOR), &pfd);



  if (!(pfd.dwFlags & PFD_NEED_PALETTE))

  {

    ScenePalette = NULL;

    return;

  };



 /*

  * Allocate memory for a color palette…

  */



  nColors = 1 << pfd.cColorBits;



  pPal = (LOGPALETTE *)malloc(sizeof(LOGPALETTE) +

                              nColors * sizeof(PALETTEENTRY));



  pPal->palVersion    = 0x300;

  pPal->palNumEntries = nColors;



 /*

  * Get the maximum values for red, green, and blue.  Then build 'nColors’

  * colors…

  */



  rmax = (1 << pfd.cRedBits) - 1;

  gmax = (1 << pfd.cGreenBits) - 1;

  bmax = (1 << pfd.cBlueBits) - 1;



  for (i = 0; i < nColors; i ++)

  {

    pPal->palPalEntry[i].peRed   = 255 * ((i >>

    pfd.cRedShift) & rmax) / rmax;

    pPal->palPalEntry[i].peGreen = 255 * ((i >>

    pfd.cGreenShift) & gmax) / gmax;

    pPal->palPalEntry[i].peBlue  = 255 * ((i >>

    pfd.cBlueShift) & bmax) / bmax;



    pPal->palPalEntry[i].peFlags = 0;

  };



 /*

  * Create, select, and realize the palette…

  */



  ScenePalette = CreatePalette(pPal);

  SelectPalette(SceneDC, ScenePalette, FALSE);

  RealizePalette(SceneDC);



  free(pPal);

}



/*

 * 'SceneProc()’ - Handle window events in the viewing window.

 */



LRESULT CALLBACK

SceneProc(HWND   hWnd,              /* I - Window triggering this event */

         UINT   uMsg,               /* I - Message type */

         WPARAM wParam,             /* I - 'word’ parameter value */

         LPARAM lParam)             /* I - 'long’ parameter value */

{

  int                       pf;     /* Pixel format ID */

  PIXELFORMATDESCRIPTOR      pfd;   /* Pixel format information */

  PAINTSTRUCT               ps;     /* WM_PAINT message info */

  RECT                      rect;   /* Current client area rectangle */



  switch (uMsg)

  {

    case WM_CREATE :

       /*

        * 'Create' message.  Get device and rendering contexts, and

        * setup the client area for OpenGL drawing…

        */



        SceneDC = GetDC(hWnd);



        pfd.nSize        = sizeof(pfd);

        pfd.nVersion     = 1;

        pfd.dwFlags      = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL

                         | PFD_DOUBLEBUFFER;

                                               /* Do OpenGL drawing */

        pfd.dwLayerMask  = PFD_MAIN_PLANE;     /* Main drawing plane */

        pfd.iPixelType   = PFD_TYPE_RGBA;      /* RGB color buffer */

        pfd.cColorBits   = 0;                  /* Best color buffer

                                               please */

        pfd.cDepthBits   = 32;                 /* Need a depth buffer */

        pfd.cStencilBits = 0;                  /* No stencil buffer */

        pfd.cAccumBits   = 0;                  /* No accumulation buffer */



        pf = ChoosePixelFormat(SceneDC, &pfd);

        if (pf == 0)

          DisplayErrorMessage('texscene was unable to choose a

          suitable pixel format!’);

        else if (!SetPixelFormat(SceneDC, pf, &pfd))

          DisplayErrorMessage('texscene was unable to set the pixel

          format!’);



        MakePalette(pf);



        SceneRC = wglCreateContext(SceneDC);

        wglMakeCurrent(SceneDC, SceneRC);



       /*

        * Load all the texture images into display lists…

        */



        LoadAllTextures();

        break;



    case WM_SIZE :

    case WM_PAINT :

       /*

        * Repaint the client area with our bitmap…

        */



        BeginPaint(hWnd, &ps);



        GetClientRect(hWnd, &rect);

        RepaintWindow(&rect);



        EndPaint(hWnd, &ps);

        break;



    case WM_COMMAND :

       /*

        * Handle menu selections…

        */



        switch (LOWORD(wParam))

        {

          case IDM_FILE_SAVEAS :

              SaveBitmapFile();

              break;

          case IDM_FILE_PRINT :

              PrintBitmap();

              break;

          case IDM_FILE_EXIT :

              DestroyWindow(SceneWindow);

              break;



          case IDM_WINDOW_TERRAIN :

             /*

              * Toggle the terrain dialog window on and off…

              */



              if (GetMenuState(GetMenu(SceneWindow), IDM_WINDOW_TERRAIN,

                               MF_BYCOMMAND) & MF_CHECKED)

              {

                CheckMenuItem(GetMenu(SceneWindow), IDM_WINDOW_TERRAIN,

                              MF_BYCOMMAND | MF_UNCHECKED);

                ShowWindow(TerrainWindow, SW_HIDE);

              }

              else

              {

                CheckMenuItem(GetMenu(SceneWindow), IDM_WINDOW_TERRAIN,

                              MF_BYCOMMAND | MF_CHECKED);

                ShowWindow(TerrainWindow, SW_SHOW);

              };

              break;

        };

        break;



    case WM_QUIT :

    case WM_CLOSE :

       /*

        * Destroy the windows and bitmaps and exit…

        */



        DestroyWindow(SceneWindow);

        DestroyWindow(TerrainWindow);



        DeleteObject(GrassDownBitmap);

        DeleteObject(GrassSelectBitmap);

        DeleteObject(GrassUpBitmap);

        DeleteObject(WaterDownBitmap);

        DeleteObject(WaterSelectBitmap);

        DeleteObject(WaterUpBitmap);

        DeleteObject(RocksDownBitmap);

        DeleteObject(RocksSelectBitmap);

        DeleteObject(RocksUpBitmap);

        DeleteObject(TreesDownBitmap);

        DeleteObject(TreesSelectBitmap);

        DeleteObject(TreesUpBitmap);

        DeleteObject(MountainsDownBitmap);

        DeleteObject(MountainsSelectBitmap);

        DeleteObject(MountainsUpBitmap);



        exit(0);

        break;



    case WM_DESTROY :

       /*

        * Release and free the device context, rendering

        * context, and color palette…

        */



        if (SceneRC)

          wglDeleteContext(SceneRC);



        if (SceneDC)

          ReleaseDC(SceneWindow, SceneDC);



        if (ScenePalette)

          DeleteObject(ScenePalette);



        PostQuitMessage(0);

        break;



    case WM_QUERYNEWPALETTE :

       /*

        * Realize the color palette if necessary…

        */



        if (ScenePalette)

        {

          SelectPalette(SceneDC, ScenePalette, FALSE);

          RealizePalette(SceneDC);



          InvalidateRect(hWnd, NULL, FALSE);

          return (TRUE);

        };

        break;



    case WM_PALETTECHANGED:

       /*

        * Reselect our color palette if necessary…

        */



        if (ScenePalette && (HWND)wParam != hWnd)

        {

          SelectPalette(SceneDC, ScenePalette, FALSE);

          RealizePalette(SceneDC);



          UpdateColors(SceneDC);

        };

        break;



    case WM_LBUTTONDOWN :

       /*

        * The left mouse button just was pressed.  If we have

        * the terrain dialog window open, then this signifies

        * the beginning of drawing.

        *

        * Otherwise, set the 'Moving’ flag to true to indicate

        * flying.

        */



        SetCapture(SceneWindow);



        if (IsWindowVisible(TerrainWindow))

        {

          DrawTerrain(LOWORD(lParam), HIWORD(lParam));

          Drawing = GL_TRUE;

        }

        else

        {

          GetCursorPos(&CenterMouseXY);

          Moving   = GL_TRUE;

          MoveTime = GetClock();

        };

        break;



    case WM_MOUSEMOVE :

       /*

        * The mouse pointer moved.  If we are in the process of

        * drawing some terrain, do it.

        *

        * Otherwise, ignore the message because we fly from the

        * main loop.

        */



        if (Drawing)

          DrawTerrain(LOWORD(lParam), HIWORD(lParam));

        break;



    case WM_LBUTTONUP :

       /*

        * The user released the left mouse button.  Stop drawing

        * or flying…

        */



        Moving  = GL_FALSE;

        Drawing = GL_FALSE;

        ReleaseCapture();



        InvalidateRect(SceneWindow, NULL, TRUE);

        break;



    default :

       /*

        * Pass all other messages through the default window

        * procedure…

        */



        return (DefWindowProc(hWnd, uMsg, wParam, lParam));

  };



  return (FALSE);

}



/*

 * 'TerrainDlgProc()' - Process messages in the terrain dialog window.

 */



UINT CALLBACK

TerrainDlgProc(HWND   hWnd,    /* I - Source window */

               UINT   uMsg,    /* I - Message type */

               WPARAM wParam,  /* I - 'word' parameter value */

               LPARAM lParam)  /* I - 'long' parameter value */

{

  HDC                   hdc;   /* Drawing context for buttons */

  LPDRAWITEMSTRUCT      lpdis; /* Button state info */

  UINT                  idCtl; /* Button ID */



  switch (uMsg)

  {

    case WM_DRAWITEM :

       /*

        * Windows wants us to draw a button.  Figure out which

        * button it is, and display as necessary…

        */



        idCtl = (UINT)wParam;

        lpdis = (LPDRAWITEMSTRUCT)lParam;

        hdc   = CreateCompatibleDC(lpdis->hDC);



        switch (idCtl)

        {

          case IDC_WATER :

              if (lpdis->itemState & ODS_SELECTED)

                SelectObject(hdc, WaterDownBitmap);

              else if (TerrainCurrent == IDC_WATER)

                SelectObject(hdc, WaterSelectBitmap);

              else

                SelectObject(hdc, WaterUpBitmap);

              break;

          case IDC_GRASS :

              if (lpdis->itemState & ODS_SELECTED)

                SelectObject(hdc, GrassDownBitmap);

              else if (TerrainCurrent == IDC_GRASS)

                SelectObject(hdc, GrassSelectBitmap);

              else

                SelectObject(hdc, GrassUpBitmap);

              break;

          case IDC_TREES :

              if (lpdis->itemState & ODS_SELECTED)

                SelectObject(hdc, TreesDownBitmap);

              else if (TerrainCurrent == IDC_TREES)

                SelectObject(hdc, TreesSelectBitmap);

              else

                SelectObject(hdc, TreesUpBitmap);

              break;

          case IDC_ROCKS :

              if (lpdis->itemState & ODS_SELECTED)

                SelectObject(hdc, RocksDownBitmap);

              else if (TerrainCurrent == IDC_ROCKS)

                SelectObject(hdc, RocksSelectBitmap);

              else

                SelectObject(hdc, RocksUpBitmap);

              break;

          case IDC_MOUNTAINS :

              if (lpdis->itemState & ODS_SELECTED)

                SelectObject(hdc, MountainsDownBitmap);

              else if (TerrainCurrent == IDC_MOUNTAINS)

                SelectObject(hdc, MountainsSelectBitmap);

              else

                SelectObject(hdc, MountainsUpBitmap);

              break;

        };



       /*

        * Stretch the bitmap to fit the button area…

        */



        StretchBlt(lpdis->hDC, lpdis->rcItem.left,

                   lpdis->rcItem.top, lpdis->rcItem.right,

                   lpdis->rcItem.bottom,

                   hdc, 0, 0, 24, 24, SRCCOPY);

        DeleteDC(hdc);

        break;



    case WM_CLOSE :

       /*

        * Close the window (hide it) and turn the check mark off

        * in the main menu.

        */



        ShowWindow(TerrainWindow, SW_HIDE);

        CheckMenuItem(GetMenu(SceneWindow), IDM_WINDOW_TERRAIN,

                      MF_BYCOMMAND | MF_UNCHECKED);

        break;



    case WM_COMMAND :

       /*

        * A button was selected - choose the new current terrain

        * type.

        */



        switch (LOWORD(wParam))

        {

          case IDC_GRASS :

          case IDC_TREES :

          case IDC_ROCKS :

          case IDC_WATER :

          case IDC_MOUNTAINS :

              TerrainCurrent = LOWORD(wParam);



              InvalidateRect(TerrainWindow, NULL, TRUE);

              UpdateWindow(TerrainWindow);

              return (TRUE);

        };

        break;

  };



  return (FALSE);

}



/*

 * 'LoadAllBitmaps()’ - Load bitmap images for the terrain control buttons.

 */



void

LoadAllBitmaps(HINSTANCE hInstance)         /* I - Process instance */

{

  GrassDownBitmap   = LoadBitmap((HANDLE)hInstance,

                                 MAKEINTRESOURCE(IDB_GRASS_DOWN));

  GrassSelectBitmap = LoadBitmap((HANDLE)hInstance,

                                 MAKEINTRESOURCE(IDB_GRASS_SELECT));

  GrassUpBitmap     = LoadBitmap((HANDLE)hInstance,

                                 MAKEINTRESOURCE(IDB_GRASS_UP));



  WaterDownBitmap   = LoadBitmap((HANDLE)hInstance,

                                 MAKEINTRESOURCE(IDB_WATER_DOWN));

  WaterSelectBitmap = LoadBitmap((HANDLE)hInstance,

                                MAKEINTRESOURCE(IDB_WATER_SELECT));

  WaterUpBitmap     = LoadBitmap((HANDLE)hInstance,

                                 MAKEINTRESOURCE(IDB_WATER_UP));



  RocksDownBitmap   = LoadBitmap((HANDLE)hInstance,

                                 MAKEINTRESOURCE(IDB_ROCKS_DOWN));

  RocksSelectBitmap = LoadBitmap((HANDLE)hInstance,

                                 MAKEINTRESOURCE(IDB_ROCKS_SELECT));

  RocksUpBitmap     = LoadBitmap((HANDLE)hInstance,

                                 MAKEINTRESOURCE(IDB_ROCKS_UP));



  TreesDownBitmap   = LoadBitmap((HANDLE)hInstance,

                                 MAKEINTRESOURCE(IDB_TREES_DOWN));

  TreesSelectBitmap = LoadBitmap((HANDLE)hInstance,

                                 MAKEINTRESOURCE(IDB_TREES_SELECT));

  TreesUpBitmap     = LoadBitmap((HANDLE)hInstance,

                                 MAKEINTRESOURCE(IDB_TREES_UP));



  MountainsDownBitmap   = LoadBitmap((HANDLE)hInstance,

                                     MAKEINTRESOURCE(IDB_MOUNTAINS_DOWN));

  MountainsSelectBitmap = LoadBitmap((HANDLE)hInstance,

                                     MAKEINTRESOURCE(IDB_MOUNTAINS_

                                     SELECT));

  MountainsUpBitmap     = LoadBitmap((HANDLE)hInstance,

                                     MAKEINTRESOURCE(IDB_MOUNTAINS_UP));

}



/*

 * 'LoadAllTextures()’ - Load texture images for the scene.

 */



void

LoadAllTextures(void)

{

  glNewList(SkyTexture = glGenLists(1), GL_COMPILE);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    TextureLoadBitmap('textures/sky.bmp’);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

  glEndList();



  glNewList(RocksTexture = glGenLists(1), GL_COMPILE);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    TextureLoadMipmap('textures/rock.bmp’);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_

    MIPMAP_LINEAR);

  glEndList();



  glNewList(GrassTexture = glGenLists(1), GL_COMPILE);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    TextureLoadMipmap('textures/grass.bmp’);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_

    MIPMAP_LINEAR);

  glEndList();



  glNewList(WaterTexture = glGenLists(1), GL_COMPILE);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    TextureLoadMipmap('textures/water.bmp’);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_

    MIPMAP_LINEAR);

  glEndList();



  glNewList(TreesTexture = glGenLists(1), GL_COMPILE);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    TextureLoadMipmap('textures/trees.bmp’);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_

    MIPMAP_LINEAR);

  glEndList();



  glNewList(MountainsTexture = glGenLists(1), GL_COMPILE);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    TextureLoadMipmap('textures/mountain.bmp’);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_

    MIPMAP_LINEAR);

  glEndList();

}



/*

 * 'UpdateNormals()’ - Update the lighting normals for the

 *                     terrain…

 */



void

UpdateNormals(void)

{

  int          x, y;           /* Terrain (x,y) location */

  GLfloat      (*n)[3],        /* Current terrain normal */

               nx, ny, nz,     /* Normal components */

               d,              /* Normal magnitude */

               *height;        /* Current terrain height */

 /*

  * Loop through the terrain arrays and regenerate the

  * lighting normals based on the terrain height.

  */



  n      = TerrainNormal[0];

  height = TerrainHeight[0];

  for (y = 0; y < (TERRAIN_SIZE - 1); y ++, n ++, height ++)

  {

    for (x = 0; x < (TERRAIN_SIZE - 1); x ++, n ++, height ++)

    {

     /*

      * Compute the cross product of the vectors above and to

      * the right (simplified for this special case).

      */



      nx = height[0] - height[1];

      ny = -1.0;

      nz = height[0] - height[TERRAIN_SIZE];



      d = -sqrt(nx * nx + ny * ny + nz * nz);



      n[0][0] = nx / d;   /* Normalize the normal vector */

      n[0][1] = ny / d;

      n[0][2] = nz / d;

    };



   /*

    * Compute the cross product of the vectors above and to

    * the left (simplified for this special case) for the last

    * column in the grid.

    */



    nx =
 
 
 
 
 
      
   


  
 

              
                  
               

              
              
              
              
          


  
  

                          
                       
                               
                              

                          
                            
                            
                            
                            
                        

                     
                              
                          
                         
                
                
                
                
                
                
                
                
                
                
                

                       height[0] -
height[-1];             ny = 
             
         
         

                            
-1.0;       nz =        height[0] - height[TERRAIN_SIZE];
                d =       -sqrt(nx * nx
+           ny         * ny + nz *
nz);         n[0][0] =     
                                         nx /
d;         /*  Normalize         the normal vector */
                n[0][1] =             ny / d;
                n[0][2] =                


   
 

                      
                    
            
               
                    
                    
                    
                     
                     
                     
                    
                    
                  


     
 

 
                      
                      
                             
                                  

                                    
                               
                                  

 
        
  
  

 
    nz /
  d;

  };          /*
  *    Set
  the     top
  row     of
  normals      to
  be          the
  same        as the
  second-to-  *
  last   row
  of  normals. */

  for (x = 
  
        
    0; x
  <

 TERRAIN_SIZE;
  x ++, n ++)
  {

  n[0][0] =    
                                
                             
                                
                                

  n[-TERRAIN_SIZE][0]; n[0][1] = 
  
       
     
  

   
  

 
             
    
  

  n[-TERRAIN_SIZE][1];

  n[0][2] =   
                                n[-TERRAIN_SIZE][2];

 };
  } /* * 'InitializeTerrain()’ - Initialize the terrain arrays… */
  void

  InitializeTerrain(void) {
  int
   x,
    y; /* Terrain (x,y) location */ /*
    *

    Fill the terrain
           array with grass… */ TerrainCurrent = 
          
      
        
        
      
      
         

   
        
    

    
     
  

   



        
 


                
                                              
                                            
                                              

                                     
                                   IDC_WATER;

  for (y = 
    

   
    
  

  
       



            
 


                                

                       
                               
                                             
                                    
                                              
                                          
                                           
                                           
                                           

 
            
  

    0; y

  < TERRAIN_SIZE; y ++)
  for
    (x = 
    
  

 
   0; x < TERRAIN_SIZE; x ++)
  {

  TerrainType[y][x] =   IDC_GRASS;

  TerrainHeight[y][x] =   GRASS_HEIGHT
                              + 0.1 *

  (rand()     %
  5);  };

 /*
  * Update the lighting normals… */ UpdateNormals(); } /* *  'draw_cell()’ - Draw
  (fill-in) a
  single

  terrain  cell… */ void draw_cell(int x,
  /*  I - Terrain X location
  */  int y) /* I -

  Terrain Y  location */ { /* * Range
  check
    the    terrain location… */ if
    (x < 0 || x
    > =   TERRAIN_SIZE ||
    y < 0 || y
    >  =    
      TERRAIN_SIZE) return; if

    (TerrainType[y][x] = 
  

 
        
  

   = 
    
  

  



          
 

 
                        
                                
                             
                             

                                  
               
                          
                                

   
  
      
       
                 
               TerrainCurrent)
        return;

        /*  Already

        the         right
        type      */
        TerrainType[y][x]      =   
                          
                                                 TerrainCurrent; /* *
        Force   a     redraw… */ InvalidateRect(SceneWindow, NULL, TRUE);
        /*    *      Set the height of the
        terrain    'cell’.                  For water, the *
                                               height is
        constant    at                 WATER_HEIGHT. For other types, * we
        add  a                  random pertubation to make the
        terrain    more                  * interesting/realistic. */ switch (TerrainCurrent)

        {  case IDC_WATER
        : TerrainHeight[y][x] = 
               
            
            
              WATER_HEIGHT; break; case
          IDC_GRASS

        :

        TerrainHeight[y][x] = 
         

       
                
        

        
        

      
      
       
               
        

         

         
        

         
        

      
       
           
        

         
        
            
              
              
            
              
              
            
              
              

            
             
                      
              

                
                                 
              
                 
                                
                 
              
              
              
                 
                                
                 
              
              
        
        

      
      
       
               
        

        
        

        
        
        
        
        
        
        
        
        
        
        
        
        
        
        

        
        

      
       
               
            
        

         
          

         
           

         
          

        
        

      
       
              
        

         
        
            
          

            
           
        
        

     
       
         GRASS_HEIGHT + 0.1 * (rand() %
        5);

        break; case IDC_TREES : TerrainHeight[y][x]= 
        
            
          

          
        
        

      
       
                   
                
            
        
                 
         
        

        

         
        TREES_HEIGHT
          + 0.1
          *  (rand()
        %
        5);
        break;
          case
          IDC_ROCKS    :
          TerrainHeight[y][x] = 
        
        

      
       
                    
             
        
                 
          
        

         
           
        

      
       
             ROCKS_HEIGHT + 0.1  * (rand()
        % 5); break;
        case

        IDC_MOUNTAINS   :
        TerrainHeight[y][x] = 
        

          
        

     
       
                
         
        

            
  

   



          
 

 
            
                           
                        
                        

                             
             
                        

   
  
      
       
                   
        MOUNTAINS_HEIGHT + 0.15 * (rand() % 5); break;
        };

        }  /*
        *  'DrawTerrain()’
        -    Draw

        a terrain
        cell
          at the given
              mouse * position. */
                void DrawTerrain(int
              mousex, /* I  -
                Horizontal mouse
              position
                */ int
              mousey)
          /* I -
              Vertical mouse position */
                { int
              i, /* Looping  var
                */ count,
              /*
                Selection count
              */
          x, y; /*
              Terrain (x,y) location */
                GLfloat *height;
              /* Current height  */
                GLuint buffer[100];
              /*
                Selection buffer
              */
          GLint viewport[4]; /*
              OpenGL viewport */ /*
                * Get
              the current OpenGL  viewport
                and make
              the
                vertical *
              mouse
          position start from
              the bottom of the
                viewport. */
              glGetIntegerv(GL_VIEWPORT, viewport); mousey = 
                 
              
                 
              
        

       
                
        

         
                    viewport[3]
                   -
                   1 - mousey; /* * Begin
        selection
        into

    a 100 'hit’
       buffer…
        * * Allow picks within 4 pixels of the current mouse position.
        */ glSelectBuffer(100, buffer); glRenderMode(GL_SELECT); glInitNames();
        glMatrixMode(GL_PROJECTION);

        glLoadIdentity(); gluPickMatrix((GLdouble)mousex,
        (GLdouble)mousey, 4.0,
                      4.0, viewport); gluPerspective(45.0,
        (float)viewport[2]

    / (float)viewport[3], 0.1,
       1000.0);
        glMatrixMode(GL_MODELVIEW); glPushMatrix(); /* * Rotate/translate for the current viewing position and
        * orientation.
        */

        glRotatef(Roll, 0.0,
        0.0,
          1.0); glRotatef(Pitch, -1.0,
          0.0, 0.0); glRotatef(Heading,
          0.0, 1.0, 0.0);
          glTranslatef(-Position[0], -Position[1], -Position[2]);
          glScalef(TERRAIN_SCALE, TERRAIN_SCALE, TERRAIN_SCALE);
              /*  *

              Draw the terrain
              into
              the selection
        buffer.
        This
  is

  * done
differently

than
 the RepaintWindow() function does * so that we can select individual
 cells

rather
than whole         * strips of one type. *
*
  The    select
                                 buffer
  has  names
                                 pushed
  on      the
                                 stack

  for    both
                                 *
  the  X
                                and
  Y      locations
                                 in

  the    terrain…
                                 */
  height = 
                                 
  TerrainHeight[0];      glPushName(0);
                                 for

  (y   = 
                                 0;
  y  <
                                 (TERRAIN_SIZE
  -      1);
                                 y

  ++,    height
                                     ++)
  {  glLoadName(y);
                                     glPushName(0);
                                     for
  (x     = 
                                     



         
 




  0;  x <
    (TERRAIN_SIZE - 1);
    x ++, height
    ++)
    { glLoadName(x); glBegin(GL_POLYGON);
  glVertex3f((GLfloat)(x

  -  TERRAIN_EDGE), height[0],
    (GLfloat)(y - TERRAIN_EDGE));
    glVertex3f((GLfloat)(x - TERRAIN_EDGE),
    height[TERRAIN_SIZE],
    (GLfloat)(y - TERRAIN_EDGE
    + 1)); glVertex3f((GLfloat)(x
    -
  TERRAIN_EDGE

  +  1), height[1],
    (GLfloat)(y - TERRAIN_EDGE));
    glVertex3f((GLfloat)(x - TERRAIN_EDGE
    +
    1), height[TERRAIN_SIZE +
    1], (GLfloat)(y -
    TERRAIN_EDGE
  +

  1));  glEnd(); };
    glPopName(); }; glPopName();
    glPopMatrix(); glFinish(); /*
    *
    Get the 'hits’
    in the selection
    buffer…
  */

  count =  
      
      
    
      
      
    glRenderMode(GL_RENDER);
  for

  (i =  
      
      
    
      
      
    
  



         
                      
 




                            
                    
                         
                                
                           
 
          
      0; i < count;
  i

  +      = 
  3)  {
  if (buffer[i] =            
  
      = 0) continue; /* * Each 'hit’ will contain the following parameters: *
    *
     0
      - count (2) * 1 - Z minimum value * 2
      - Z maximum value * 3 - Y
      location

      in  terrain * 4
      -  X
      location  in terrain */

      x =         buffer[i + 4];

      y =           
      buffer[i  + 3]; i
      + =   
    

   
     buffer[i]; /* * Fill-in the 4 corners of the selected
    cell… */ draw_cell(x, y); draw_cell(x + 1, y); draw_cell(x, y +
    1); draw_cell(x + 1, y
    +

    1);  /* * Update
    lighting  normals
    for  the terrain. */

    UpdateNormals();  }; } /* * 'FlyTerrain()’ - Fly using the given mouse

    position.  */ void FlyTerrain(int   mousex, /* I - Horizontal mouse
    position  */ int mousey)
    /*  I - Vertical
  mouse

 position
  */ { RECT rect; /* Current client rectangle */ GLfloat movex, movey; /* Scale
  mouse movement */ double curtime,
  /*

  Current time  in seconds */ distance; /* Distance to move
  */
    GLfloat  cheading,
    /*  Cosine
    of  heading
  */
sheading,

/*
 Sine of heading */ cpitch, /* Cosine
 of

pitch
*/
spitch;
  /*   Sine of      pitch */ /* * Get

 the
  current system time to figure out how
  far

  to  move.

  */ curtime =      
    GetClock(); distance =     10.0 *
    (curtime
      -    MoveTime);
      MoveTime =       
    

 
      
  

  



         
 


          
                    

 
       
  

   curtime; /* * See how far the mouse
      pointer is from the 'center’ (click) *
    position.

  */ movex = 
                  0.05 * (mousex - CenterMouseXY.x);

  movey = 

 
     
  

    

 
             
           
            
   
  

   0.05
  *
    (mousey - CenterMouseXY.y);
        /*  *
        Adjust
    roll, pitch, and
        heading  according to the current * mouse inputs
        and
    orientation. */ Roll
        + =       
        
     movex; Pitch
        + =    movey * cos(Roll *
        M_PI
    / 180.0); Heading
        + =       
        
  



           
                    
 


               
                           

                             
                            
                               
                   
                
                 

 
           
   movey * sin(Roll * M_PI / 180.0); if (Heading
  <

  0.0) Heading
  + =     

 
         
  
            
  

   
  

  
  
  
     
                
     
                  

  
  
   
           
     
    

       
       
       
    
                 
                 
      

   
              
           
              
        
    
               
          360.0; else if
    (Heading

    > = 
    360.0)
    Heading - =         360.0; if
    (Pitch
      <
      -180.0)

      Pitch + =          
      
        
        
            
                     
                       
            
                     
                         
              
                     
                       
              
                       
                         
        
      

      
    
    
  
  

 
       360.0; else if
  (Pitch

  > = 180.0)
  Pitch - =    360.0; if (Roll <
  -180.0)
    Roll + = 
      

   
           
    
        
         
         
          
          
    

    360.0;  else if (Roll
    > =   180.0)
    Roll -= 

   
            
    

     
       
       
         

   
          
    

    
  



         
 


                
                           

                          
                
                      
                          
                   
                          
                            
                            

 
        360.0; /* * Move based upon the
  current

  orientation…   */
  cheading = cos(Heading * M_PI / 180.0);
  sheading = 

 
            sin(Heading *
  M_PI /
  180.0);

  cpitch = cos(Pitch * M_PI / 180.0);
  spitch =     

 
           
      sin(Pitch
  *

  M_PI    / 180.0);
  Position[0]   +=   distance * sheading * cpitch;
  Position[2] -=       distance

  * cheading * cpitch;
    Position[1] += 
   distance * spitch; /*
    * Redraw the

  window using the new
    position and orientation…
  */ GetClientRect(SceneWindow, &rect); RepaintWindow(&rect); }
    /* * 'RepaintWindow()’

  - Redraw the client
    area with our
  scene. */ void RepaintWindow(RECT *rect)
    /* I -

 Client
  area rectangle */ { int i; /*
  Looping

  var  */ int x, y; /*
  Terrain  (x,y) location */ int last_type;
  /*    Previous terrain type */ int
  *type;    /* Current terrain type */

  GLfloat *height, /* Current terrain height */
  (*n)[3]; /* Current terrain normal */ static
  GLfloat sky_top[4][3]=   

 
           
  

   
  



          
 


          

                          
            { /*        Sky coordinates */ { -TERRAIN_EDGE,
  TERRAIN_SIZE          *   0.8, -TERRAIN_EDGE }, { TERRAIN_EDGE,
  TERRAIN_SIZE          *       0.8, -TERRAIN_EDGE }, { TERRAIN_EDGE,
  TERRAIN_SIZE      *     0.8, TERRAIN_EDGE }, { -TERRAIN_EDGE,
               TERRAIN_SIZE     * 0.8, TERRAIN_EDGE } };
  static GLfloat       sky_bottom[4][3] =
                         
          
           
            
           
  
       
  {
    { -TERRAIN_EDGE, 0.0, -TERRAIN_EDGE },
    {  TERRAIN_EDGE, 0.0, -TERRAIN_EDGE },
    {  TERRAIN_EDGE, 0.0,  TERRAIN_EDGE },
    { -TERRAIN_EDGE, 0.0,  TERRAIN_EDGE }
  };
  static GLfloat            sunpos[4] = { 0.0, 1.0, 0.0, 0.0 };
  static GLfloat            suncolor[4] = { 64.0, 64.0, 64.0, 1.0 };
  static GLfloat            sunambient[4] = { 0.001, 0.001, 0.001, 1.0 };

 /*
  * Reset the viewport and clear the window to light blue…
  */

  glViewport(0, 0, rect->right, rect->bottom);

  glClearColor(0.5, 0.5, 1.0, 1.0);

  glEnable(GL_DEPTH_TEST);

  if (Moving || Drawing)
  {
   /*
    * Don’t texture while flying or drawing; it’s too slow…
    * Also, draw to the back buffer for smooth animation.
    */

    glDisable(GL_TEXTURE_2D);
    glDrawBuffer(GL_BACK);
  }
  else
  {
   /*
    * Enable textures when we’ve stopped moving or drawing.
    * This generates a nice scene that we can printout or
    * save to a bitmap file…
    *
    * Because it takes longer, we draw to the front buffer
    * so the user can see some progress…
    */

    glEnable(GL_TEXTURE_2D);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
    glDrawBuffer(GL_FRONT);
  };

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

 /*
  * Setup viewing transformations for the current position and
  * orientation…
  */

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(45.0, (float)rect->right / (float)rect->bottom,
                 0.1, 1000.0);

  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();
    glRotatef(Roll, 0.0, 0.0, 1.0);
    glRotatef(Pitch, -1.0, 0.0, 0.0);
    glRotatef(Heading, 0.0, 1.0, 0.0);
    glTranslatef(-Position[0],
                 -Position[1],
                 -Position[2]);
    glScalef(TERRAIN_SCALE, TERRAIN_SCALE, TERRAIN_SCALE);

    if (!(Moving || Drawing))
    {
     /*
      * Draw the sky…
      */

      glDisable(GL_LIGHTING);
      glCallList(SkyTexture);
      glBegin(GL_QUAD_STRIP);
        for (i = 0; i < 4; i ++)
        {
          glTexCoord2f((float)i, 0.0);
          glVertex3fv(sky_bottom[i]);
          glTexCoord2f((float)i, 0.8);
          glVertex3fv(sky_top[i]);
        };

        glTexCoord2f(4.0, 0.0);
        glVertex3fv(sky_bottom[0]);

        glTexCoord2f(4.0, 0.8);
        glVertex3fv(sky_top[0]);
      glEnd();

      glBegin(GL_TRIANGLE_FAN);
        glTexCoord2f(0.5, 1.0);
        glVertex3f(0.0, TERRAIN_SIZE, 0.0);

        for (i = 0; i < 4; i ++)
        {
          glTexCoord2f((float)i, 0.8);
          glVertex3fv(sky_top[i]);
        };

        glTexCoord2f(4.0, 0.8);
        glVertex3fv(sky_top[0]);
      glEnd();
    };

   /*
    * Setup lighting…
    */

    glEnable(GL_LIGHTING);
    glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);

    glEnable(GL_LIGHT0);
    glLightfv(GL_LIGHT0, GL_POSITION, sunpos);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, suncolor);
    glLightfv(GL_LIGHT0, GL_AMBIENT, sunambient);

    if (Moving || Drawing)
      glEnable(GL_COLOR_MATERIAL);
    else
      glDisable(GL_COLOR_MATERIAL);

   /*
    * Then the terrain…
    */

    type   = TerrainType[0];
    height = TerrainHeight[0];
    n      = TerrainNormal[0];
    for (y = 0; y < (TERRAIN_SIZE - 1); y ++)
    {
      last_type = -1;

      for (x = 0; x < TERRAIN_SIZE; x ++, type ++, height ++, n ++)
      {
        if (last_type != *type)
        {

    /*
     * If the type of terrain changes, end any existing
     * strip of quads and reset color/texture parameters…
     */

     if (last_type != -1)
       glEnd();

     switch (*type)
     {
       case IDC_WATER :
           if (Moving || Drawing)
             glColor3f(0.0, 0.0, 0.5);
           else
             glCallList(WaterTexture);
           break;
       case IDC_GRASS :
           if (Moving || Drawing)
             glColor3f(0.0, 0.5, 0.0);
           else
             glCallList(GrassTexture);
           break;
       case IDC_ROCKS :
           if (Moving || Drawing)
             glColor3f(0.25, 0.25, 0.25);
           else
             glCallList(RocksTexture);
           break;
       case IDC_TREES :
           if (Moving || Drawing)
             glColor3f(0.0, 0.25, 0.0);
           else
             glCallList(TreesTexture);
           break;
       case IDC_MOUNTAINS :
            if (Moving || Drawing)
              glColor3f(0.2, 0.1, 0.05);
            else
              glCallList(MountainsTexture);
            break;
      };

      glBegin(GL_QUAD_STRIP);
      if (last_type != -1)
      {
       /*
        * Start from the previous location to prevent
        * holes…
        */ 
 
        glTexCoord2i(x * 2 - 2, y * 2);
        glNormal3fv(n[-1]);
        glVertex3f((GLfloat)(x - TERRAIN_EDGE - 1),
                   height[-1],
                   (GLfloat)(y - TERRAIN_EDGE));
        glTexCoord2i(x * 2 - 2, y * 2 + 2);
        glNormal3fv(n[TERRAIN_SIZE - 1]);
        glVertex3f((GLfloat)(x - TERRAIN_EDGE - 1),
                   height[TERRAIN_SIZE - 1],
                   (GLfloat)(y - TERRAIN_EDGE + 1));
          };
          last_type = *type;
        }; 
 
        glTexCoord2i(x * 2, y * 2);
        glNormal3fv(n[0]);
        glVertex3f((GLfloat)(x - TERRAIN_EDGE),
                   height[0],
                   (GLfloat)(y - TERRAIN_EDGE));
        glTexCoord2i(x * 2, y * 2 + 2);
        glNormal3fv(n[TERRAIN_SIZE]);
        glVertex3f((GLfloat)(x - TERRAIN_EDGE),
                   height[TERRAIN_SIZE],
                   (GLfloat)(y - TERRAIN_EDGE + 1));
     };

     glEnd();
   };
 glPopMatrix();

/*
 * While we fly or draw we’re double-buffering.  Swap buffers
 * as necessary…
 */

 glFinish();
 if (Moving || Drawing)
   SwapBuffers(SceneDC);
}

/*
 * 'SaveBitmapFile()’ - Save the currently displayed scene to disk.
 */

void
SaveBitmapFile(void)
{
  char           title[256],      /* Title of file */
                 filename[256],   /* Name of file */
                 directory[256];  /* Current directory */PRE>


Previous Table of Contents Next