Loads brick.ppm into a texture and displays it. Toggle it by pressing t.
main.cpp:
// Alexandri Zavodny
// CSE 40166: Computer Graphics, Fall 2010
// Example: Simple texturing example: use 't' to enable textures and rotate
// using an arcball camera model.
#include <math.h>
#include <GL/glut.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <vector>
using namespace std;
// GLOBAL VARIABLES ////////////////////////////////////////////////////////////
static size_t windowWidth = 640;
static size_t windowHeight = 480;
static float aspectRatio;
GLint leftMouseButton, rightMouseButton; //status of the mouse buttons
int mouseX = 0, mouseY = 0; //last known X and Y of the mouse
float cameraTheta, cameraPhi, cameraRadius; //camera position in spherical coordinates
float x, y, z; //camera position in cartesian coordinates
//some global variables for our textures
unsigned char *brickTexData; //the actual image data
GLuint brickTexHandle; //a handle to the texture in our OpenGL context
int brickTexWidth, brickTexHeight; //variables for the image width/height
bool useTexturing = false; //toggled with 't'
// readPPM() ///////////////////////////////////////////////////////////////////
//
// This function reads an ASCII PPM, returning true if the function succeeds and
// false if it fails. If it succeeds, the variables imageWidth and
// imageHeight will hold the width and height of the read image, respectively.
//
// It's not terribly robust.
//
// Returns the image as an unsigned character array containing
// imageWidth*imageHeight*3 entries (for that many bytes of storage).
//
// NOTE: this function expects imageData to be UNALLOCATED, and will allocate
// memory itself. If the function fails (returns false), imageData
// will be set to NULL and any allocated memory will be automatically deallocated.
//
////////////////////////////////////////////////////////////////////////////////
bool readPPM(string filename, int &imageWidth, int &imageHeight, unsigned char* &imageData)
{
FILE *fp = fopen(filename.c_str(), "r");
int temp, maxValue;
fscanf(fp, "P%d", &temp);
if(temp != 3)
{
fprintf(stderr, "Error: PPM file is not of correct format! (Must be P3, is P%d.)\n", temp);
fclose(fp);
return false;
}
//got the file header right...
fscanf(fp, "%d", &imageWidth);
fscanf(fp, "%d", &imageHeight);
fscanf(fp, "%d", &maxValue);
//now that we know how big it is, allocate the buffer...
imageData = new unsigned char[imageWidth*imageHeight*3];
if(!imageData)
{
fprintf(stderr, "Error: couldn't allocate image memory. Dimensions: %d x %d.\n", imageWidth, imageHeight);
fclose(fp);
return false;
}
//and read the data in.
for(int j = 0; j < imageHeight; j++)
{
for(int i = 0; i < imageWidth; i++)
{
fscanf(fp, "%hhd", &imageData[(j*imageWidth+i)*3+0]);
fscanf(fp, "%hhd", &imageData[(j*imageWidth+i)*3+1]);
fscanf(fp, "%hhd", &imageData[(j*imageWidth+i)*3+2]);
}
}
fclose(fp);
return true;
}
// registerOpenGLTexture() /////////////////////////////////////////////////////
//
// Attempts to register an image buffer with OpenGL. Sets the texture handle
// appropriately upon success, and uses a number of default texturing
// values. This function only provides the basics: just sets up this image
// as an unrepeated 2D texture.
//
////////////////////////////////////////////////////////////////////////////////
bool registerOpenGLTexture(unsigned char *imageData, int texWidth, int texHeight, GLuint &texHandle)
{
//first off, get a real texture handle.
glGenTextures(1, &texHandle);
//make sure that future texture functions affect this handle
glBindTexture(GL_TEXTURE_2D, texHandle);
//set this texture's color to be multiplied by the surface colors --
// GL_MODULATE instead of GL_REPLACE allows us to take advantage of OpenGL lighting
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
//set the minification ang magnification functions to be linear; not perfect
// but much better than nearest-texel (GL_NEAREST).
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
//Set the texture to repeat in S and T -- though it doesn't matter here
// because our texture coordinates are always in [0,0] to [1,1].
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
//actually transfer the texture to the GPU!
glTexImage2D(GL_TEXTURE_2D, //still working with 2D textures, obv.
0, //not using mipmapping, so this is the highest level.
GL_RGB, //we're going to provide OpenGL with R, G, and B components...
texWidth, //...of this width...
texHeight, //...and this height.
0, //give it a border of 0 pixels (none)
GL_RGB, //and store it, internally, as RGB (OpenGL will convert to floats between 0.0 and 1.0f)
GL_UNSIGNED_BYTE, //this is the format our data is in, and finally,
imageData); //there's the data!
//whoops! i guess this function can't fail. in an ideal world, there would
//be checks with glGetError() that we could use to determine if this failed.
return true;
}
// recomputeOrientation() //////////////////////////////////////////////////////
//
// This function updates the camera's position in cartesian coordinates based
// on its position in spherical coordinates. Should be called every time
// cameraTheta, cameraPhi, or cameraRadius is updated.
//
////////////////////////////////////////////////////////////////////////////////
void recomputeOrientation()
{
x = cameraRadius * sinf(cameraTheta)*sinf(cameraPhi);
z = cameraRadius * -cosf(cameraTheta)*sinf(cameraPhi);
y = cameraRadius * -cosf(cameraPhi);
glutPostRedisplay();
}
// resizeWindow() //////////////////////////////////////////////////////////////
//
// GLUT callback for window resizing. Resets GL_PROJECTION matrix and viewport.
//
////////////////////////////////////////////////////////////////////////////////
void resizeWindow(int w, int h)
{
aspectRatio = w / (float)h;
windowWidth = w;
windowHeight = h;
//update the viewport to fill the window
glViewport(0, 0, w, h);
//update the projection matrix with the new window properties
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0,aspectRatio,0.1,100000);
}
// mouseCallback() /////////////////////////////////////////////////////////////
//
// GLUT callback for mouse clicks. We save the state of the mouse button
// when this is called so that we can check the status of the mouse
// buttons inside the motion callback (whether they are up or down).
//
////////////////////////////////////////////////////////////////////////////////
void mouseCallback(int button, int state, int thisX, int thisY)
{
//update the left and right mouse button states, if applicable
if(button == GLUT_LEFT_BUTTON)
leftMouseButton = state;
else if(button == GLUT_RIGHT_BUTTON)
rightMouseButton = state;
//and update the last seen X and Y coordinates of the mouse
mouseX = thisX;
mouseY = thisY;
}
// mouseMotion() ///////////////////////////////////////////////////////////////
//
// GLUT callback for mouse movement. We update cameraPhi, cameraTheta, and/or
// cameraRadius based on how much the user has moved the mouse in the
// X or Y directions (in screen space) and whether they have held down
// the left or right mouse buttons. If the user hasn't held down any
// buttons, the function just updates the last seen mouse X and Y coords.
//
////////////////////////////////////////////////////////////////////////////////
void mouseMotion(int x, int y)
{
if(leftMouseButton == GLUT_DOWN)
{
cameraTheta += (x - mouseX)*0.005;
cameraPhi += (y - mouseY)*0.005;
// make sure that phi stays within the range (0, M_PI)
if(cameraPhi <= 0)
cameraPhi = 0+0.001;
if(cameraPhi >= M_PI)
cameraPhi = M_PI-0.001;
recomputeOrientation(); //update camera (x,y,z) based on (radius,theta,phi)
} else if(rightMouseButton == GLUT_DOWN) {
double totalChangeSq = (x - mouseX) + (y - mouseY);
cameraRadius += totalChangeSq*0.01;
//limit the camera radius to some reasonable values so the user can't get lost
if(cameraRadius < 2.0)
cameraRadius = 2.0;
if(cameraRadius > 10.0)
cameraRadius = 10.0;
recomputeOrientation(); //update camera (x,y,z) based on (radius,theta,phi)
}
mouseX = x;
mouseY = y;
}
// initScene() /////////////////////////////////////////////////////////////////
//
// A basic scene initialization function; should be called once after the
// OpenGL context has been created. Doesn't need to be called further.
//
////////////////////////////////////////////////////////////////////////////////
void initScene()
{
glEnable(GL_DEPTH_TEST);
float lightCol[4] = { 1, 1, 1, 1};
float ambientCol[4] = {0.3, 0.3, 0.3, 1.0};
glLightfv(GL_LIGHT0,GL_DIFFUSE,lightCol);
glLightfv(GL_LIGHT0, GL_AMBIENT, ambientCol);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glShadeModel(GL_SMOOTH);
//glShadeModel(GL_FLAT);
}
// renderScene() ///////////////////////////////////////////////////////////////
//
// GLUT callback for scene rendering. Sets up the modelview matrix, renders
// a teapot to the back buffer, and switches the back buffer with the
// front buffer (what the user sees).
//
////////////////////////////////////////////////////////////////////////////////
void renderScene(void)
{
//update the modelview matrix based on the camera's position
glMatrixMode(GL_MODELVIEW); //make sure we aren't changing the projection matrix!
glLoadIdentity();
gluLookAt(x, y, z, //camera is located at (x,y,z)
0, 0, 0, //camera is looking at (0,0,0)
0.0f,1.0f,0.0f); //up vector is (0,1,0) (positive Y)
//update the light position after we set the modelview matrix;
//lights get transformed by the modelview matrix just like every other point!
float lPosition[4] = { 10, 10, 10, 1 };
glLightfv(GL_LIGHT0,GL_POSITION,lPosition);
//clear the render buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//enable texture if necessary
if(useTexturing)
{
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, brickTexHandle);
} else {
glDisable(GL_TEXTURE_2D);
}
//draw a simple quad in the XY plane, with texture coordinates and normals for lighting.
glBegin(GL_QUADS);
glNormal3f(0,0,1);
glTexCoord2f(0,0);
glVertex3f(-2.5,1.6125,0);
glNormal3f(0,0,1);
glTexCoord2f(1,0);
glVertex3f(2.5,1.6125,0);
glNormal3f(0,0,1);
glTexCoord2f(1,1);
glVertex3f(2.5,-1.6125,0);
glNormal3f(0,0,1);
glTexCoord2f(0,1);
glVertex3f(-2.5,-1.6125,0);
glEnd();
//push the back buffer to the screen
glutSwapBuffers();
}
// normalKeys() ////////////////////////////////////////////////////////////////
//
// GLUT keyboard callback.
//
////////////////////////////////////////////////////////////////////////////////
void normalKeys(unsigned char key, int x, int y)
{
if(key == 'q' || key == 'Q')
exit(0);
if(key == 't' || key == 'T')
useTexturing = !useTexturing;
}
// main() //////////////////////////////////////////////////////////////////////
//
// Program entry point. Does not process command line arguments.
//
////////////////////////////////////////////////////////////////////////////////
int main(int argc, char **argv)
{
//create a double-buffered GLUT window at (50,50) with predefined windowsize
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(50,50);
glutInitWindowSize(windowWidth,windowHeight);
glutCreateWindow("it's a briiiick (doo dooo do) wall (doo dooo do)");
//give the camera a 'pretty' starting point!
cameraRadius = 7.0f;
cameraTheta = 2.80;
cameraPhi = 2.0;
recomputeOrientation();
//register callback functions...
glutKeyboardFunc(normalKeys);
glutDisplayFunc(renderScene);
glutIdleFunc(renderScene);
glutReshapeFunc(resizeWindow);
glutMouseFunc(mouseCallback);
glutMotionFunc(mouseMotion);
//do some basic OpenGL setup
initScene();
//read in our file and register our texture
bool success = readPPM("brick.ppm", brickTexWidth, brickTexHeight, brickTexData);
//NOTE THAT THIS NEEDS TO HAPPEN AFTER THE OPENGL CONTEXT HAS BEEN INITIALIZED.
// which it has, thankfully.
if(success)
registerOpenGLTexture(brickTexData, brickTexWidth, brickTexHeight, brickTexHandle);
//and enter the GLUT loop, never to exit.
glutMainLoop();
return(0);
}