Help with Textured Heightfield Terrain

I’m having trouble getting my heightfield terrain to work properly. I’m using the code from the ‘OpenGL Game Programming’ book and when I run it, I get white horizontal bars across my window. Can someone help me out and tell me what’s wrong?

Here’s the program (I’m using VS.NET 03, by the way):
(my test2, land, water bitmaps are 32X32 pixels. test2.bmp is grayscale while land.bmp and water.bmp have color in them)

#define WIN32_LEAN_AND_MEAN // trim the excess fat from Windows

////// Includes
#include <windows.h> // standard Windows app include
#include <gl/gl.h> // standard OpenGL include
#include <gl/glu.h> // OpenGL utilties
//#include <gl/glaux.h> // OpenGL auxiliary functions
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

////// Defines
#define BITMAP_ID 0x4D42
#define MAP_X 32
#define MAP_Z 32
#define MAP_SCALE 20.0f
#define PI 3.14159

////// Global Variables
HDC g_HDC; // global device context
bool fullScreen = false; //true = full screen; false = windowed
bool keyPressed[256]; //holds true for keys that are pressed

float angle = 0.0f; //camera angle
float radians = 0.0f; //camera angle in radians
float waterHeight = 154.0f; //height of water
bool waterDir = true; //used to animate water
//true = up, false = down

//////Mouse/Camera Variables
int mouseX, mouseY; //mouse coordinates
float cameraX, cameraY, cameraZ; //camera coordinates
float lookX, lookY, lookZ; //camera look-at coordinates

////// Textrue Information
BITMAPINFOHEADER bitmapInfoHeader; //temp bitmap info header
BITMAPINFOHEADER landInfo; //land texture info header
BITMAPINFOHEADER waterInfo; //water texture info header

unsigned char* imageData; //the map image data
unsigned char* landTexture; //land texture data
unsigned char* waterTexture; //water texture data
unsigned int land; //the land texture object
unsigned int water; //the water texture object

////// Terrain Data
float terrain[MAP_X][MAP_Z][3];


// function to set the pixel format for the device context
void SetupPixelFormat(HDC hDC)
{
	int nPixelFormat; // our pixel format index

	static PIXELFORMATDESCRIPTOR pfd = {
	sizeof(PIXELFORMATDESCRIPTOR), // size of structure
	1, // default version
	PFD_DRAW_TO_WINDOW | // window drawing support
	PFD_SUPPORT_OPENGL | // OpenGL support
	PFD_DOUBLEBUFFER, // double buffering support
	PFD_TYPE_RGBA, // RGBA color mode
	32, // 32 bit color mode
	0, 0, 0, 0, 0, 0, // ignore color bits, non-palettized mode
	0, // no alpha buffer
	0, // ignore shift bit
	0, // no accumulation buffer
	0, 0, 0, 0, // ignore accumulation bits
	16, // 16 bit z-buffer size
	0, // no stencil buffer
	0, // no auxiliary buffer
	PFD_MAIN_PLANE, // main drawing plane
	0, // reserved
	0, 0, 0 }; // layer masks ignored

	nPixelFormat = ChoosePixelFormat(hDC, &pfd); // choose best matching pixel format

	SetPixelFormat(hDC, nPixelFormat, &pfd); // set pixel format to device context
}

// the Windows Procedure event handler
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static HGLRC hRC; // rendering context
	static HDC hDC; // device context
	int width, height; // window width and height
	int oldMouseX, oldMouseY; //old mouse coordinates

	switch(message)
	{
		case WM_QUIT:
		return 0;
		break;
		case WM_CREATE: // window is being created

		hDC = GetDC(hwnd); // get current window's device context
		g_HDC = hDC;
		SetupPixelFormat(hDC); // call our pixel format setup function

		// create rendering context and make it current
		hRC = wglCreateContext(hDC);
		wglMakeCurrent(hDC, hRC);

		return 0;
		break;

		case WM_CLOSE: // windows is closing

		// deselect rendering context and delete it
		wglMakeCurrent(hDC, NULL);
		wglDeleteContext(hRC);

		// send WM_QUIT to message queue
		PostQuitMessage(0);

		return 0;
		break;

		case WM_SIZE:
		height = HIWORD(lParam); // retrieve width and height
		width = LOWORD(lParam);

		if (height==0) // don't want a divide by zero
		{
			height=1;
		}

		glViewport(0, 0, width, height); // reset the viewport to new dimensions
		glMatrixMode(GL_PROJECTION); // set projection matrix current matrix
		glLoadIdentity(); // reset projection matrix

		// calculate aspect ratio of window
		gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,1.0f,1000.0f);

		glMatrixMode(GL_MODELVIEW); // set modelview matrix
		glLoadIdentity(); // reset modelview matrix

		return 0;
		break;

		case WM_KEYDOWN: //is a key pressed?
		keyPressed[wParam] = true;
		return 0;
		break;

		case WM_KEYUP:
		keyPressed[wParam] = false;
		return 0;
		break;

		case WM_MOUSEMOVE:
		//save old mouse coordinates
		oldMouseX = mouseX;
		oldMouseY = mouseY;

		//get mouse cooridnates from Windows
		mouseX = LOWORD(1);
		mouseY = HIWORD(1);

		//these linies limit the camera's range
		if( mouseY < 200 )
		mouseY = 200;
		if( mouseY > 450 )
		mouseY = 450;

		if( (mouseX - oldMouseX) > 0 ) //mouse moved to the right
		angle += 3.0f;
		else if( (mouseX - oldMouseX) < 0 ) //mouse moved to the left
		angle -= 3.0f;

		return 0;
		break;

		default:
		break;
	}

return (DefWindowProc(hwnd, message, wParam, lParam));
}

unsigned char *LoadBitmapFile(char *filename, BITMAPINFOHEADER * bitmapInfoHeader)
{
	FILE *filePtr; //the file pointer
	BITMAPFILEHEADER bitmapFileHeader; //bitmap file header
	unsigned char *bitmapImage; //bitmap image data
	int imageIdx = 0; //image index counter
	unsigned char tempRGB; //swap variable

	//open filename in "read binary" mode
	filePtr = fopen(filename, "rb");
	if( filePtr == NULL)
	return NULL;

	//read the bitmap file header
	fread(&bitmapFileHeader, sizeof(BITMAPFILEHEADER), 1, filePtr);

	//verify that this is a bitmap by checking for the universal bitmap id
	if( bitmapFileHeader.bfType != BITMAP_ID )
	{
		fclose(filePtr);
		return NULL;
	}

	//read the bitmap information header
	fread(bitmapInfoHeader, sizeof(BITMAPFILEHEADER), 1, filePtr);

	//move file pointer to beginning of bitmap data
	fseek(filePtr, bitmapFileHeader.bfOffBits, SEEK_SET);

	//allocate enough memory for the bitmap image data
	bitmapImage = (unsigned char*)malloc(bitmapInfoHeader->biSizeImage);

	//verify memory allocation
	if(!bitmapImage)
	{
		free(bitmapImage);
		fclose(filePtr);
		return NULL;
	}

	//read in the bitmap image data
	fread(bitmapImage, 1, bitmapInfoHeader->biSizeImage, filePtr);

	//make sure bitmap image data was read
	if (bitmapImage == NULL)
	{
		fclose(filePtr);
		return NULL;
	}

	//swap the R and B values to get RGB since the bitmap color format is in BGR
	for( imageIdx = 0; imageIdx < bitmapInfoHeader->biSizeImage; imageIdx += 3 )
	{
		tempRGB = bitmapImage[imageIdx];
		bitmapImage[imageIdx] = bitmapImage[imageIdx + 2];
		bitmapImage[imageIdx + 2] = tempRGB;
	}

	//close the file and return the bitmap image data
	fclose(filePtr);

return bitmapImage;
}

void InitializeTerrain()
{
	// loop through all of the heightfield points, calculating the coordinates for each point
	for (int z = 0; z < MAP_Z; z++)
	{
		for (int x = 0; x < MAP_X; x++)
		{
			terrain[x][z][0] = float(x)*MAP_SCALE;
			terrain[x][z][1] = (float)imageData[(z*MAP_Z+x)*3];
			terrain[x][z][2] = -float(z)*MAP_SCALE;
		}
	}
}

bool LoadTextures()
{
	//load the land-texture data
	landTexture = LoadBitmapFile("test2.bmp", &landInfo);
	if (!landTexture)
	return false;

	//load the water-texture data
	waterTexture = LoadBitmapFile("water.bmp", &waterInfo);
	if (!waterTexture)
	return false;

	//generate the land texture as a mipmap
	glGenTextures(1, &land);
	glBindTexture(GL_TEXTURE_2D, land);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, landInfo.biWidth, landInfo.biHeight, GL_RGB, GL_UNSIGNED_BYTE, landTexture);

	//generate the water texture as a mipmap
	glGenTextures(1, &water);
	glBindTexture(GL_TEXTURE_2D, water);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, waterInfo.biWidth, waterInfo.biHeight, GL_RGB, GL_UNSIGNED_BYTE, waterTexture);

	return true;
}

void Render()
{
	radians = float(PI*(angle-90.0f)/180.0f);

	//calculate the camera's position
	cameraX = lookX + sin(radians)*mouseY; //multiplying by mouseY makes the
	cameraZ = lookZ + cos(radians)*mouseY; //camera get closer/
	//farther away with mouseY
	cameraY = lookY + mouseY/2.0f;

	//calculate the camera look-at cooridnates as the center of the terrain map
	lookX = (MAP_X*MAP_SCALE)/2.0f;
	lookY = 150.0f;
	lookZ = -(MAP_Z*MAP_SCALE)/2.0f;

	//clear screen and depth buffer
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();

	//set the camera position
	gluLookAt(cameraX, cameraY, cameraZ, lookX, lookY, lookZ, 0.0, 1.0, 0.0);

	//set the current texture to the land texture
	glBindTexture(GL_TEXTURE_2D, land);

	//we are going to loop through all of our terrain's data points,
	//but we only want to draw one triangle strip for each set along the x axis.
	for (int z = 0; z < MAP_Z-1; z++ )
	{
		glBegin(GL_TRIANGLE_STRIP);
		for (int x = 0; x < MAP_X-1; x++ )
		{
			//for each vertex, we calculate the grayscale shade color,
			//we set the texture coordinate, and we draw the vertex.

			/*
			the vertices are drawn in this order:

			0 --->1
			    /
			   /
			  /
			2 --->3
			       */

			//draw vertex 0
			glColor3f(terrain[x][z][1]/255.0f, terrain[x][z][1]/255.0f, terrain[x][z][1]/255.0f);
			glTexCoord2f(0.0f, 0.0f);
			glVertex3f(terrain[x][z][0], terrain[x][z][1], terrain[x][z][2]);

			//draw vertex 1
			glTexCoord2f(1.0f,0.0f);
			glColor3f(terrain[x+1][z][1]/255.0f, terrain[x+1][z][1]/255.0f, terrain[x+1][z][1]/255.0f);
			glVertex3f(terrain[x+1][z][0], terrain[x+1][z][1], terrain[x+1][z][2]);

			//draw vertex 2
			glTexCoord2f(0.0f,1.0f);
			glColor3f(terrain[x][z+1][1]/255.0f, terrain[x][z+1][1]/255.0f, terrain[x][z+1][1]/255.0f);
			glVertex3f(terrain[x][z+1][0], terrain[x][z+1][1], terrain[x][z+1][2]);

			//draw vertex 3
			glColor3f(terrain[x+1][z+1][1]/255.0f, terrain[x+1][z+1][1]/255.0f, terrain[x+1][z+1][1]/255.0f);
			glTexCoord2f(1.0f,1.0f);
			glVertex3f(terrain[x+1][z+1][0], terrain[x+1][z+1][1], terrain[x+1][z+1][2]);
		}
	glEnd();
	}

	//enable blending
	glEnable(GL_BLEND);

	//enable read-only depth buffer
	glDepthMask(GL_FALSE);

	//set the blend function to what we use for transparency
	glBlendFunc(GL_SRC_ALPHA, GL_ONE);

	glColor4f(0.5f, 0.5f, 1.0f, 0.7f); //set color to a transparent blue
	glBindTexture(GL_TEXTURE_2D, water); //set texture to the water texture

	//draw water as one large quad surface
	glBegin(GL_QUADS);
	glTexCoord2f(0.0f, 0.0f); //lower-left corner
	glVertex3f(terrain[0][0][0], waterHeight, terrain[0][0][2]);

	glTexCoord2f(10.0f, 0.0f); //lower-right corner
	glVertex3f(terrain[MAP_X-1][0][0], waterHeight, terrain[MAP_X-1][0][2]);

	glTexCoord2f(10.0f, 10.0f); //upper-right corner
	glVertex3f(terrain[MAP_X-1][MAP_Z-1][0], waterHeight, terrain[MAP_X-1][MAP_Z-1][2]);

	glTexCoord2f(10.0f, 10.0f); //upper-left corner
	glVertex3f(terrain[0][MAP_Z-1][0], waterHeight, terrain[0][MAP_Z-1][2]);
	glEnd();

	//set back to normal depth buffer mode (writeable)
	glDepthMask(GL_TRUE);

	//disable blending
	glDisable(GL_BLEND);

	//animate the water
	if( waterHeight > 155.0f )
	waterDir = false;
	else if( waterHeight < 154.0f )
	waterDir = true;

	if( waterDir )
	waterHeight += 0.01f;
	else
	waterHeight -= 0.01f;

	glFlush();
	SwapBuffers(g_HDC); //bring back buffer to foreground
}

void Initialize()
{
	glClearColor(0.0f, 0.0f,  0.0f, 0.0f); //clear to black
	glShadeModel(GL_SMOOTH); //use smooth shading
	glEnable(GL_DEPTH_TEST); //hidden surface removal
	glEnable(GL_CULL_FACE); //do not calculate inside of polys
	glFrontFace(GL_CCW); //counterclockwise polygons are out

	glEnable(GL_TEXTURE_2D); //enable 2D texturing
	imageData = LoadBitmapFile("test2.bmp", &bitmapInfoHeader);

	//initialize the terrain data and load the textures
	InitializeTerrain();
	LoadTextures();
}
// the main windows entry point
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
WNDCLASSEX windowClass; // window class
HWND    hwnd; // window handle
MSG    msg; // message
bool    done; // flag saying when our app is complete
DWORD    dwExStyle; // Window Extended Style
DWORD    dwStyle; // Window Style
RECT    windowRect;

// temp var's
int width = 800;
int height = 600;
int bits = 32;

fullScreen = FALSE;

windowRect.left=(long)0; // Set Left Value To 0
windowRect.right=(long)width; // Set Right Value To Requested Width
windowRect.top=(long)0; // Set Top Value To 0
windowRect.bottom=(long)height; // Set Bottom Value To Requested Height

// fill out the window class structure
windowClass.cbSize = sizeof(WNDCLASSEX);
windowClass.style = CS_HREDRAW | CS_VREDRAW;
windowClass.lpfnWndProc = WndProc;
windowClass.cbClsExtra = 0;
windowClass.cbWndExtra = 0;
windowClass.hInstance = hInstance;
windowClass.hIcon = LoadIcon(NULL, IDI_APPLICATION); // default icon
windowClass.hCursor = LoadCursor(NULL, IDC_ARROW); // default arrow
windowClass.hbrBackground = NULL; // don't need background
windowClass.lpszMenuName = NULL; // no menu
windowClass.lpszClassName = "MyClass";
windowClass.hIconSm = LoadIcon(NULL, IDI_WINLOGO); // windows logo small icon

// register the windows class
if (!RegisterClassEx(&windowClass))
return 0;

if (fullScreen) // fullscreen?
{
	DEVMODE dmScreenSettings; // device mode
	memset(&dmScreenSettings,0,sizeof(dmScreenSettings));
	dmScreenSettings.dmSize = sizeof(dmScreenSettings);
	dmScreenSettings.dmPelsWidth = width; // screen width
	dmScreenSettings.dmPelsHeight = height; // screen height
	dmScreenSettings.dmBitsPerPel = bits; // bits per pixel
	dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;

	//
	if (ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
	{
		// setting display mode failed, switch to windowed
		MessageBox(NULL, "Display mode failed", NULL, MB_OK);
		fullScreen=FALSE;
	}
}

	if (fullScreen) // Are We Still In Fullscreen Mode?
	{
		dwExStyle=WS_EX_APPWINDOW; // Window Extended Style
		dwStyle=WS_POPUP; // Windows Style
		ShowCursor(FALSE); // Hide Mouse Pointer
	}
	else
	{
		dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; // Window Extended Style
		dwStyle=WS_OVERLAPPEDWINDOW; // Windows Style
	}

	AdjustWindowRectEx(&windowRect, dwStyle, FALSE, dwExStyle); // Adjust Window To True Requested Size

	// class registered, so now create our window
	hwnd = CreateWindowEx(NULL, // extended style
	"MyClass", // class name
	"OpenGL Application", // app name
	dwStyle | WS_CLIPCHILDREN |
	WS_CLIPSIBLINGS,
	0, 0, // x,y coordinate
	windowRect.right - windowRect.left,
	windowRect.bottom - windowRect.top, // width, height
	NULL, // handle to parent
	NULL, // handle to menu
	hInstance, // application instance
	NULL); // no extra params

	// check if window creation failed (hwnd would equal NULL)
	if (!hwnd)
	return 0;

	ShowWindow(hwnd, SW_SHOW); // display the window
	UpdateWindow(hwnd); // update the window

	done = false; // intialize the loop condition variable

	//BITMAPINFOHEADER bitmapInfoHeader; //bitmap info header
	//unsigned char* bitmapData; //the bitmap data

	//bitmapData = LoadBitmapFile("test.bmp", &bitmapInfoHeader);
	//glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
	//glRasterPos2i(100,100);
	//glDrawPixels(bitmapInfoHeader.biWidth, bitmapInfoHeader.biHeight, GL_RGB, GL_UNSIGNED_BYTE, bitmapData);

	Initialize();

	// main message loop
	while (!done)
	{
		PeekMessage(&msg, hwnd, NULL, NULL, PM_REMOVE);

		if (msg.message == WM_QUIT) // do we receive a WM_QUIT message?
		done = true; // if so, time to quit the application
		else
		{
			// do rendering here
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear screen and depth buffer
			glLoadIdentity(); // reset modelview matrix

			//angle = angle + 0.1f; // increase our rotation angle counter
			//if (angle >= 360.0f) // if we've gone in a circle, reset counter
			//angle = 0.0f;

			Render();
			SwapBuffers(g_HDC); // bring backbuffer to foreground

			TranslateMessage(&msg); // translate and dispatch to event queue
			DispatchMessage(&msg);
		}
	}

	if (fullScreen)
	{
		ChangeDisplaySettings(NULL,0); // If So Switch Back To The Desktop
		ShowCursor(TRUE); // Show Mouse Pointer
	}

	return msg.wParam;
} 

If you can target some portions of your code it might help.

agreed. this is definitely a case of “less is more”.

at a glance one problem area is you strip loop. when you run across your strip rows (or columns) there’s no need to issue 4 vertex commands, only 2. try doing your zigzag from (x,z) to (x,z+1), or from (x,z+1) to (x,z), where x is in [0,xmax] and z is in [0,zmax) (since we add 1 to z).

surely it wasn’t in the book this way.

Sorry about that, guys. These functions are the ones that deal with the heightfield (I took the camera code out, as well). As for the strip loop, that’s how it is in the book.

  
#define WIN32_LEAN_AND_MEAN // trim the excess fat from Windows

////// Includes
#include <windows.h> // standard Windows app include
#include <gl/gl.h> // standard OpenGL include
#include <gl/glu.h> // OpenGL utilties
//#include <gl/glaux.h> // OpenGL auxiliary functions
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

////// Defines
#define BITMAP_ID 0x4D42
#define MAP_X 32
#define MAP_Z 32
#define MAP_SCALE 20.0f
#define PI 3.14159

////// Global Variables
HDC g_HDC; // global device context
bool fullScreen = false; //true = full screen; false = windowed
bool keyPressed[256]; //holds true for keys that are pressed

float angle = 0.0f; //camera angle
float radians = 0.0f; //camera angle in radians
float waterHeight = 154.0f; //height of water
bool waterDir = true; //used to animate water
//true = up, false = down

////// Textrue Information
BITMAPINFOHEADER bitmapInfoHeader; //temp bitmap info header
BITMAPINFOHEADER landInfo; //land texture info header
BITMAPINFOHEADER waterInfo; //water texture info header

unsigned char* imageData; //the map image data
unsigned char* landTexture; //land texture data
unsigned char* waterTexture; //water texture data
unsigned int land; //the land texture object
unsigned int water; //the water texture object

////// Terrain Data
float terrain[MAP_X][MAP_Z][3];

unsigned char *LoadBitmapFile(char *filename, BITMAPINFOHEADER * bitmapInfoHeader)
{
	FILE *filePtr; //the file pointer
	BITMAPFILEHEADER bitmapFileHeader; //bitmap file header
	unsigned char *bitmapImage; //bitmap image data
	int imageIdx = 0; //image index counter
	unsigned char tempRGB; //swap variable

	//open filename in "read binary" mode
	filePtr = fopen(filename, "rb");
	if( filePtr == NULL)
	return NULL;

	//read the bitmap file header
	fread(&bitmapFileHeader, sizeof(BITMAPFILEHEADER), 1, filePtr);

	//verify that this is a bitmap by checking for the universal bitmap id
	if( bitmapFileHeader.bfType != BITMAP_ID )
	{
		fclose(filePtr);
		return NULL;
	}

	//read the bitmap information header
	fread(bitmapInfoHeader, sizeof(BITMAPFILEHEADER), 1, filePtr);

	//move file pointer to beginning of bitmap data
	fseek(filePtr, bitmapFileHeader.bfOffBits, SEEK_SET);

	//allocate enough memory for the bitmap image data
	bitmapImage = (unsigned char*)malloc(bitmapInfoHeader->biSizeImage);

	//verify memory allocation
	if(!bitmapImage)
	{
		free(bitmapImage);
		fclose(filePtr);
		return NULL;
	}

	//read in the bitmap image data
	fread(bitmapImage, 1, bitmapInfoHeader->biSizeImage, filePtr);

	//make sure bitmap image data was read
	if (bitmapImage == NULL)
	{
		fclose(filePtr);
		return NULL;
	}

	//swap the R and B values to get RGB since the bitmap color format is in BGR
	for( imageIdx = 0; imageIdx < bitmapInfoHeader->biSizeImage; imageIdx += 3 )
	{
		tempRGB = bitmapImage[imageIdx];
		bitmapImage[imageIdx] = bitmapImage[imageIdx + 2];
		bitmapImage[imageIdx + 2] = tempRGB;
	}

	//close the file and return the bitmap image data
	fclose(filePtr);

return bitmapImage;
}

void InitializeTerrain()
{
	// loop through all of the heightfield points, calculating the coordinates for each point
	for (int z = 0; z < MAP_Z; z++)
	{
		for (int x = 0; x < MAP_X; x++)
		{
			terrain[x][z][0] = float(x)*MAP_SCALE;
			terrain[x][z][1] = (float)imageData[(z*MAP_Z+x)*3];
			terrain[x][z][2] = -float(z)*MAP_SCALE;
		}
	}
}

bool LoadTextures()
{
	//load the land-texture data
	landTexture = LoadBitmapFile("test2.bmp", &landInfo);
	if (!landTexture)
	return false;

	//load the water-texture data
	waterTexture = LoadBitmapFile("water.bmp", &waterInfo);
	if (!waterTexture)
	return false;

	//generate the land texture as a mipmap
	glGenTextures(1, &land);
	glBindTexture(GL_TEXTURE_2D, land);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, landInfo.biWidth, landInfo.biHeight, GL_RGB, GL_UNSIGNED_BYTE, landTexture);

	//generate the water texture as a mipmap
	glGenTextures(1, &water);
	glBindTexture(GL_TEXTURE_2D, water);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, waterInfo.biWidth, waterInfo.biHeight, GL_RGB, GL_UNSIGNED_BYTE, waterTexture);

	return true;
}

void Render()
{
	//clear screen and depth buffer
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();

	//set the current texture to the land texture
	glBindTexture(GL_TEXTURE_2D, land);

	//we are going to loop through all of our terrain's data points,
	//but we only want to draw one triangle strip for each set along the x axis.
	for (int z = 0; z < MAP_Z-1; z++ )
	{
		glBegin(GL_TRIANGLE_STRIP);
		for (int x = 0; x < MAP_X-1; x++ )
		{
			//for each vertex, we calculate the grayscale shade color,
			//we set the texture coordinate, and we draw the vertex.

			/*
			the vertices are drawn in this order:

			0 --->1
			    /
			   /
			  /
			2 --->3
			       */

			//draw vertex 0
			glColor3f(terrain[x][z][1]/255.0f, terrain[x][z][1]/255.0f, terrain[x][z][1]/255.0f);
			glTexCoord2f(0.0f, 0.0f);
			glVertex3f(terrain[x][z][0], terrain[x][z][1], terrain[x][z][2]);

			//draw vertex 1
			glTexCoord2f(1.0f,0.0f);
			glColor3f(terrain[x+1][z][1]/255.0f, terrain[x+1][z][1]/255.0f, terrain[x+1][z][1]/255.0f);
			glVertex3f(terrain[x+1][z][0], terrain[x+1][z][1], terrain[x+1][z][2]);

			//draw vertex 2
			glTexCoord2f(0.0f,1.0f);
			glColor3f(terrain[x][z+1][1]/255.0f, terrain[x][z+1][1]/255.0f, terrain[x][z+1][1]/255.0f);
			glVertex3f(terrain[x][z+1][0], terrain[x][z+1][1], terrain[x][z+1][2]);

			//draw vertex 3
			glColor3f(terrain[x+1][z+1][1]/255.0f, terrain[x+1][z+1][1]/255.0f, terrain[x+1][z+1][1]/255.0f);
			glTexCoord2f(1.0f,1.0f);
			glVertex3f(terrain[x+1][z+1][0], terrain[x+1][z+1][1], terrain[x+1][z+1][2]);
		}
	glEnd();
	}

	//enable blending
	glEnable(GL_BLEND);

	//enable read-only depth buffer
	glDepthMask(GL_FALSE);

	//set the blend function to what we use for transparency
	glBlendFunc(GL_SRC_ALPHA, GL_ONE);

	glColor4f(0.5f, 0.5f, 1.0f, 0.7f); //set color to a transparent blue
	glBindTexture(GL_TEXTURE_2D, water); //set texture to the water texture

	//draw water as one large quad surface
	glBegin(GL_QUADS);
	glTexCoord2f(0.0f, 0.0f); //lower-left corner
	glVertex3f(terrain[0][0][0], waterHeight, terrain[0][0][2]);

	glTexCoord2f(10.0f, 0.0f); //lower-right corner
	glVertex3f(terrain[MAP_X-1][0][0], waterHeight, terrain[MAP_X-1][0][2]);

	glTexCoord2f(10.0f, 10.0f); //upper-right corner
	glVertex3f(terrain[MAP_X-1][MAP_Z-1][0], waterHeight, terrain[MAP_X-1][MAP_Z-1][2]);

	glTexCoord2f(10.0f, 10.0f); //upper-left corner
	glVertex3f(terrain[0][MAP_Z-1][0], waterHeight, terrain[0][MAP_Z-1][2]);
	glEnd();

	//set back to normal depth buffer mode (writeable)
	glDepthMask(GL_TRUE);

	//disable blending
	glDisable(GL_BLEND);

	//animate the water
	if( waterHeight > 155.0f )
	waterDir = false;
	else if( waterHeight < 154.0f )
	waterDir = true;

	if( waterDir )
	waterHeight += 0.01f;
	else
	waterHeight -= 0.01f;

	glFlush();
	SwapBuffers(g_HDC); //bring back buffer to foreground
}

void Initialize()
{
	glClearColor(0.0f, 0.0f,  0.0f, 0.0f); //clear to black
	glShadeModel(GL_SMOOTH); //use smooth shading
	glEnable(GL_DEPTH_TEST); //hidden surface removal
	glEnable(GL_CULL_FACE); //do not calculate inside of polys
	glFrontFace(GL_CCW); //counterclockwise polygons are out

	glEnable(GL_TEXTURE_2D); //enable 2D texturing
	imageData = LoadBitmapFile("test2.bmp", &bitmapInfoHeader);

	//initialize the terrain data and load the textures
	InitializeTerrain();
	LoadTextures();
}

which book did you take this code from?

one of these?

http://glbook.gamedev.net/

If you can post a screeshot that would help out alot…

earthquad, yes. it’s the one entitled ‘OpenGL Game Programming’

Mars_9999, of what?

Of you smiling near your computer :smiley:

well, there’s a beginning opengl game programming, more opengl game programming, just plain opengl game programming, and last, but certainly not least, opengl es game development. although i’m pretty sure you aren’t referring to the latter.

Originally posted by scryedzxp:
[b] earthquad, yes. it’s the one entitled ‘OpenGL Game Programming’

Mars_9999, of what? [/b]
Of what the messed up rendering looks like. If you can’t host it use

http://imageshack.us/

to host a screenshot.

very very weird. I didn’t save the changes I made yesterday so I applied the same changes just a couple of minutes ago and the horizontal bars are gone! However, it still looks weird. What’s going on?

earthquad, it’s the plain opengl game programming.

Mars_9999, here’s the new results:

I really think it has something to do with the bitmaps I’m using. Here’s what they look like:
(test2.bmp)

(land.bmp)

(water.bmp)

The 3 bitmaps are 32x31. The book says to use 32x32, but I really don’t think it matters.

I always thought that bitmap dimensions had to be in powers of 2.

> fread(bitmapInfoHeader, sizeof(BITMAPFILEHEADER), 1, filePtr);
> …
> bitmapImage = (unsigned char*)malloc(bitmapInfoHeader->biSizeImage);

Is it a typo? (shouldn’t it be BITMAPINFOHEADER?)
Even if it is BITMAPINFOHEADER - using biSizeImage still make no sense.
This field is normally used to store the compressed size, for bitmaps without compression biSizeImage is often ==0.
(and malloc(0) does produce valid pointer…)

> //swap the R and B values to get RGB since the bitmap color format is in BGR

What for? :confused:
Just use GL_BGR as format of the pixel data.
(or GL_BGR_EXT if you stuck with OpenGL 1.1 header)

> for( imageIdx = 0; imageIdx < bitmapInfoHeader->biSizeImage; imageIdx += 3 )
Besides using biSizeImage, this is ok - in case if the image width is multiplie of 4.

> The 3 bitmaps are 32x31. The book says to use 32x32, but I really don’t think it matters.

As long as you call gluBuild2DMipmaps - it should work even if there is no support for non-power-of-2 textures.
It does resampling itself.

I got it working. Thanks for the help, Serge K. The book I was using had so many typos in it.