GLFW3 Basecode with FPS Camera Controls

Basecode is funny thing – when you start a new project, do you really start from scratch? A complete blank slate? Or do you make a copy of the last project you worked on which is similar and modify it? Often, you’re going to want to start from some pre-existing functional base, but what’s stable and functional enough? Do you really want to go with a framework like Cinder or Processing to hold your code? Or go with a full-on engine like Unity or Unreal Engine 4 or some other engine?

I’m going to write a game at some point in the future, and I want to go it (almost) alone – I don’t want to be locked into someone elses constructs and patterns, or drag-and-drop functionality in which I have absolutely no idea how it works – I want to think for myself and create what’s basically my own engine, where I understand how it fits together and how each piece works. This doesn’t necessarily mean that everything needs to be worked out from first principles, but it should be possible to make all the important architectural decisions. This means that I want precise control over:

  • At least one OpenGL window, with controllable context details (preferably multiple windows with a shared projection)
  • Painless keyboard and mouse handlers
  • File handling of common types (load and use this 3D model/sound file/settings file)
  • Help with prototyping via simple drawing calls

Which brings us back to basecode being a funny thing – you get to make the architectural decisions, and live with the consequences. If you decide to go with an engine, then you’re going to learn the engine – not the fundamental technologies or aspects of the code that make the engine work. So if you grab some fantastic engine and you go:

  1. Load this spaceship model, which is made of these different materials,
  2. There’s a light which is at (1000, 200, 300) in world space (and perhaps a dozen other lights),
  3. Draw the spaceship from my (i.e the camera’s) location.

But what does that actually teach you, as a developer? How do you load the model from file? How is the lighting model applied to the vertices? Where the hell is the spaceship in relation to you, let alone the surface normals of the spaceship with regard to the light-source(s) with regard to the camera? In an engine, you don’t care – you let the engine work it out for you, and you learn nothing. Or maybe you learn the engine – which means you learn to trust someone else to think instead of you having to think for yourself.

Which finally brings us back to basecode being a funny thing… I’ve been thinking about this for weeks, and below is the OpenGL/GLFW3 basecode I’ve written to open a window, draw some grids for orientation, and allow for ‘FPS-esque’ mouse and keyboard controls. The main.cpp is listed below, which shows you how the program itself runs – everything else you’ll need to look at for yourself – but I promise you this:

  • Every single piece of this code is clear in its use and serves a purpose.
  • Every single piece of this code performs its job in the simplest, most straight forward manner possible. If the option is to be clever or readable, then I pick readable every time. Saying that, I think I used an inline if-statement once i.e. “if (raining) ? putUpUmbrella() : keepUmbrellaDown();”. Honestly, when you see it, you’ll be okay.
  • Every single piece of this code is documented to explain not only WHAT the code is doing, but (where appropriate) WHY it is doing it. When I used to work as as Subsystem Integration and Test engineer, we would write software build instructions with the goal that your Mum should be able to build the software image from the simple, accurate, non-ambiguous instructions. If you didn’t think your Mum could build it, then you re-worked the instructions until you thought that she could.

I’ll add some additional utility classes to this over time, but for now, this basecode will get a window with FPS controls up and running and display some grids via shaders for orientation – and everything should be simple, straight-forward and clear. Enjoy!

Code::Blocks projects for both Windows and Linux (libraries included for Windows) can be found here: GLFW3_Basecode_Nov_2014.7z.

Update – Feb 2015: There were issues using this code in Visual Studio 2010 as it doesn’t support strongly typed enums or the R” notation (although VS2012 onwards does), and the libraries packaged were the Code::Blocks versions (which was intended – the above version is specifically for Code::Blocks) – so here’s a modified & fully working Visual Studio 2010 version: GLFW3-Basecode-VS2010.7z.

/***
Project: GLFW3 Basecode
Version: 0.5
Author : r3dux
Date   : 21/1/2014
Purpose: Basecode to setup an OpenGL context with FPS camera controls and draw some grids.
***/
 
#include <iostream>
 
// Define that we're using the static version of GLEW (glew32s) so that it gets built
// into our final executable.
// NOTE: This MUST be defined before importing GLEW!
#define GLEW_STATIC
 
// Include the GL Extension Wrangler. Note: GLEW should always be the very first include
#include <GL/glew.h>
 
#include <GLFW/glfw3.h>                 // Include GL Framework. Note: This pulls in GL.h for us.
 
// Include the GL Mathematics library
#define GLM_FORCE_RADIANS               // We must work in radians in newer versions of GLM...
#include <glm/glm.hpp>                  // ...so now that's defined we can import GLM itself.
 
// Include our custom classes
#include "Camera.h"
#include "Grid.h"
#include "Utils.h"
 
// Save ourselves some typing...
using std::cout;
using std::endl;
using glm::vec3;
using glm::vec4;
using glm::mat4;
using glm::mat3;
 
// ---------- Global variables ----------
 
// Window and projection settings
GLsizei windowWidth       = 800;
GLsizei windowHeight      = 600;
float vertFieldOfViewDegs = 45.0f;
float nearClipDistance    = 1.0f;
float farClipDistance     = 2000.0f;
 
// Misc
int  frameCount = 0;              // How many frames we've drawn
int  frameRate  = 60;             // Target frame rate -we'll assume a 60Hz refresh for now
bool leftMouseButtonDown = false; // We'll only look around when the left mouse button is down
 
// Matricies
mat4 projectionMatrix; // The projection matrix is used to perform the 3D to 2D conversion i.e. it maps from eye space to clip space.
mat4 viewMatrix;       // The view matrix maps the world coordinate system into eye cordinates (i.e. world space to eye space)
mat4 modelMatrix;      // The model matrix maps an object's local coordinate system into world coordinates (i.e. model space to world space)
 
// Pointers to two grids
Grid *upperGrid, *lowerGrid;
 
// Camera. Params: location, rotation (degrees), window width & height
Camera camera(vec3(0.0f), vec3(0.0f), windowWidth, windowHeight);
 
// Callback function to resize the window and set the viewport to the correct size
void resizeWindow(GLFWwindow *window, GLsizei newWidth, GLsizei newHeight)
{
    // Keep track of the new width and height of the window
    windowWidth  = float(newWidth);
    windowHeight = float(newHeight);
 
    // Recalculate the projection matrix
    projectionMatrix = glm::perspective(vertFieldOfViewDegs, GLfloat(windowWidth) / GLfloat(windowHeight), nearClipDistance, farClipDistance);
 
    // Viewport is the entire window
    glViewport(0, 0, windowWidth, windowHeight);
 
    // Update the midpoint location in the camera class because it uses these values, too
    camera.updateWindowMidpoint(windowWidth, windowHeight);
}
 
// Callback function to handle keypresses
void handleKeypress(GLFWwindow* window, int key, int scancode, int action, int mods)
{
    // User hit ESC? Set the window to close
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
    {
        glfwSetWindowShouldClose(window, GL_TRUE);
    }
    else
    {
        camera.handleKeypress(key, action);
    }
}
 
// Callback function to handle mouse movement
void handleMouseMove(GLFWwindow *window, double mouseX, double mouseY)
{
    // We'll only look around when the left mouse button is down
    if (leftMouseButtonDown)
    {
        camera.handleMouseMove(window, mouseX, mouseY);
    }
}
 
// Callback function to handle mouse button presses
void handleMouseButton(GLFWwindow *window, int button, int action, int mods)
{
    // Button press involves left mouse button?
    if (button == GLFW_MOUSE_BUTTON_1)
    {
        if (action == GLFW_PRESS)
        {
            glfwSetCursorPos(window, windowWidth / 2, windowHeight / 2);
            leftMouseButtonDown = true;
        }
        else // Action must be GLFW_RELEASE
        {
            leftMouseButtonDown = false;
        }
    }
}
 
// Function to set up our OpenGL rendering context
void initGL(GLFWwindow *window)
{
    // ---------- Initialise GLEW ----------
 
    // Enable glewExperimental which ensures that all extensions with valid entry points will be exposed.
    glewExperimental = true;
 
    // Note: We MUST have an OpenGL rendering context open to initialise GLEW successfully!
    GLenum err = glewInit();
    if (GLEW_OK != err)
    {
        cout << "GLEW error: " << glewGetErrorString(err) << endl;
        glfwTerminate();
    }
    cout << "GLEW intialised successfully. Using GLEW version: " << glewGetString(GLEW_VERSION) << endl << endl;
 
    // Depending on the OpenGL context settings, calling glewInit() can sometimes cause a GL_INVALID_ENUM error.
    // As this issue isn't really our code's fault, we'll check the error here to clear it.
    //
    // Cause: In a core profile context, GL_EXTENSIONS is an invalid constant to pass to glGetString (...). You
    // must use the new glGetStringi (...) function. GLEW does not do this by default, given a core context
    // without being informed to use glGetStringi (...), GLEW will use glGetString (...) and will cause GL to
    // generate a GL_INVALID_ENUM error. In order to get GLEW to use glGetStringi (...) (which you should ONLY
    // do in an OpenGL 3.0+ context), set glewExperimental = true; before calling glewInit (...).
    //
    // Source: http://stackoverflow.com/questions/19453439/solved-opengl-error-gl-invalid-enum-0x0500-while-glewinit
    checkGLError("glewInit - harmless / ignore");
 
    // ---------- Setup OpenGL Options ----------
 
    glViewport( 0, 0, GLsizei(windowWidth), GLsizei(windowHeight) ); // Viewport is entire window
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);                            // Clear to black with full alpha
    glEnable(GL_DEPTH_TEST);                                         // Enable depth testing
    glDepthFunc(GL_LEQUAL);                                          // Specify depth testing function
    glClearDepth(1.0);                                               // Clear the full extent of the depth buffer (default)
    glEnable(GL_CULL_FACE);                                          // Enable face culling
    glCullFace(GL_BACK);                                             // Cull back faces of polygons
    glFrontFace(GL_CCW);                                             // Counter-clockwise winding indicates a forward facing polygon (default)
 
    // ---------- Setup GLFW Callback Functions ----------
 
    glfwSetWindowSizeCallback(window, resizeWindow);                 // Register window resize functiom
    glfwSetKeyCallback(window, handleKeypress);                      // Register keyboard handler function
    glfwSetCursorPosCallback(window, handleMouseMove);               // Register mouse movement handler function
    glfwSetMouseButtonCallback(window, handleMouseButton);           // Register mouse button handler function
 
    // ---------- Setup GLFW Options ----------
 
    glfwSwapInterval(1);                                             // Swap buffers every frame (i.e. lock to VSync)
    glfwSetInputMode(window, GLFW_CURSOR_DISABLED, GL_FALSE);        // Do not hide the mouse cursor
    glfwSetWindowPos(window, 200, 200);                              // Push the top-left of the window out from the top-left corner of the screen
    glfwSetCursorPos(window, windowWidth / 2, windowHeight / 2);     // Move the mouse cursor to the centre of the window
 
    checkGLError("InitGL");
}
 
// Function to perform our drawing
void drawFrame()
{
    // Move the camera
    camera.move(1.0f/ frameRate);
 
    // ---------- Matrix operations ----------
 
    // Reset our View matrix
    viewMatrix = mat4(1.0f);
 
    // Perform camera rotation
    viewMatrix = glm::rotate(viewMatrix, camera.getXRotationRads(), X_AXIS);
    viewMatrix = glm::rotate(viewMatrix, camera.getYRotationRads(), Y_AXIS);
 
    // Translate to our camera position
    viewMatrix = glm::translate(viewMatrix, -camera.getLocation() );
 
    // Create an identity matrix for the model matrix
    modelMatrix = mat4(1.0f);
 
    // ---------- Drawing operations ----------
 
    mat4 mvpMatrix = projectionMatrix * viewMatrix * modelMatrix;
    upperGrid->draw(mvpMatrix);
    lowerGrid->draw(mvpMatrix);
}
 
int main()
{
    // ----- Initialiise GLFW, specify window hints & open a context -----
 
    // IMPORTANT: glfwInit resets all window hints, so we must call glfwInit FIRST and THEN we supply window hints!
    if (!glfwInit())
    {
        cout << "glfwInit failed!" << endl;
        exit(-1);
    }
 
    // Further reading on GLFW window hints: http://www.glfw.org/docs/latest/window.html#window_hints
 
    // If we want to use a a core profile (i.e. no legacy fixed-pipeline functionality) or if we want to
    // use forward compatible mode (i.e. only non-deprecated features of a given OpenGL version available)
    // then we MUST specify the MAJOR.MINOR context version we want to use FIRST!
    //glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    //glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 4);
    //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
    //glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
 
    // Ask for 4x Anti-Aliasing
    glfwWindowHint(GLFW_SAMPLES, 4);
 
    // Create a window. Params: width, height, title, *monitor, *share
    GLFWwindow* window = glfwCreateWindow(GLsizei(windowWidth), GLsizei(windowHeight), "GLFW3 Basecode | Use WSAD to move & LMB to look around - Nov 2014 | r3dux.org ", NULL, NULL);
    if (!window)
    {
        cout << "Failed to create window - bad context MAJOR.MINOR version?" << endl;
        getKeypressThenExit();
    }
 
    // Make the current OpenGL context active
    glfwMakeContextCurrent(window);
 
    // Display the details of our OpenGL window
    displayWindowProperties(window);
 
    // -------------- Set up our OpenGL settings ---------------
 
    initGL(window);
 
    // ---------- Set up our grids ----------
 
    // Instantiate our grids. Params: Width, Depth, level (i.e. location of y-axis), number of grid lines
    upperGrid = new Grid(1000.0f, 1000.0f,  200.0f, 20);
    lowerGrid = new Grid(1000.0f, 1000.0f, -200.0f, 20);
 
    // ---------- Set up our matricies ----------
 
    // Specify the projection matrix
    projectionMatrix = glm::perspective(vertFieldOfViewDegs, GLfloat(windowWidth) / GLfloat(windowHeight), nearClipDistance, farClipDistance);
 
    // Reset the view and model and view matrices to identity
    viewMatrix  = mat4(1.0f);
    modelMatrix = mat4(1.0f);
 
    // ---------- Main loop ----------
 
    while ( !glfwWindowShouldClose(window) )
    {
        frameCount++;
 
        // Clear the screen and depth buffer
        glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
 
        // Draw our frame
        drawFrame();
 
        // Swap the back and front buffers to display the frame we just rendered
        glfwSwapBuffers(window);
 
        // Poll for input
        glfwPollEvents();
    }
 
    // Check the final error state
    // NOTE: This MUST be called while we still have a valid rendering context (i.e. before we call glfwTerminate() )
    checkGLError("End");
 
    // Destroy the window and exit
    glfwDestroyWindow(window);
    glfwTerminate();
 
    return 0;
}

A C++ Camera Class for Simple OpenGL FPS Controls

This is the third post of three, where we finally get to create a Camera class which encapsulates all the important properties of a camera suitable for FPS controls. I could, and indeed did, have this written to just use three floats for the camera position, three for the rotation, three for the movement speed etc – but it makes more sense to use a vector class to encapsulate those values into a single item and provide methods for easy manipulation, so that’s what I’ve done.

The end result of this is that although the Camera class now depends on the Vec3 class, the Camera class itself is now more concise and easier to use. If you don’t like the coupling you can easily break it and return to individual values, but I think I prefer it this way. Oh, and this class is designed to work with GLFW, although it could be very easily modified to remove that requirement and be used with SDL or something instead. In fact, we only ever use the glfwSetMousePos(x, y) method to reset the mouse position to the centre of the screen each frame!

Anyways, let’s look at the header first to see the properties and methods of the class:

Camera.h Header

#ifndef CAMERA_H
#define CAMERA_H
 
#include <iostream>
#include <math.h>         // Used only for sin() and cos() functions
 
#include <GL/glfw.h>      // Include OpenGL Framework library for the GLFW_PRESS constant only!
 
#include "Vec3.hpp"       // Include our custom Vec3 class
 
class Camera
{
    protected:
        // Camera position
        Vec3<double> position;
 
        // Camera rotation
        Vec3<double> rotation;
 
        // Camera movement speed. When we call the move() function on a camera, it moves using these speeds
        Vec3<double> speed;
 
        double movementSpeedFactor; // Controls how fast the camera moves
        double pitchSensitivity;    // Controls how sensitive mouse movements affect looking up and down
        double yawSensitivity;      // Controls how sensitive mouse movements affect looking left and right
 
        // Window size in pixels and where the midpoint of it falls
        int windowWidth;
        int windowHeight;
        int windowMidX;
        int windowMidY;
 
        // Method to set some reasonable default values. For internal use by the class only.
        void initCamera();
 
    public:
        static const double TO_RADS; // The value of 1 degree in radians
 
        // Holding any keys down?
        bool holdingForward;
        bool holdingBackward;
        bool holdingLeftStrafe;
        bool holdingRightStrafe;
 
        // Constructor
        Camera(float windowWidth, float windowHeight);
 
        // Destructor
        ~Camera();
 
        // Mouse movement handler to look around
        void handleMouseMove(int mouseX, int mouseY);
 
        // Method to convert an angle in degress to radians
        const double toRads(const double &angleInDegrees) const;
 
        // Method to move the camera based on the current direction
        void move(double deltaTime);
 
        // --------------------------------- Inline methods ----------------------------------------------
 
        // Setters to allow for change of vertical (pitch) and horizontal (yaw) mouse movement sensitivity
        float getPitchSensitivity()            { return pitchSensitivity;  }
        void  setPitchSensitivity(float value) { pitchSensitivity = value; }
        float getYawSensitivity()              { return yawSensitivity;    }
        void  setYawSensitivity(float value)   { yawSensitivity   = value; }
 
        // Position getters
        Vec3<double> getPosition() const { return position;        }
        double getXPos()           const { return position.getX(); }
        double getYPos()           const { return position.getY(); }
        double getZPos()           const { return position.getZ(); }
 
        // Rotation getters
        Vec3<double> getRotation() const { return rotation;        }
        double getXRot()           const { return rotation.getX(); }
        double getYRot()           const { return rotation.getY(); }
        double getZRot()           const { return rotation.getZ(); }
};
 
#endif // CAMERA_H

Now for the implementation:

Camera.cpp Class

#include "Camera.h"
 
const double Camera::TO_RADS = 3.141592654 / 180.0; // The value of 1 degree in radians
 
Camera::Camera(float theWindowWidth, float theWindowHeight)
{
	initCamera();
 
	windowWidth  = theWindowWidth;
	windowHeight = theWindowHeight;
 
	// Calculate the middle of the window
	windowMidX = windowWidth  / 2.0f;
	windowMidY = windowHeight / 2.0f;
 
	glfwSetMousePos(windowMidX, windowMidY);
}
 
Camera::~Camera()
{
	// Nothing to do here - we don't need to free memory as all member variables
	// were declared on the stack.
}
 
void Camera::initCamera()
{
	// Set position, rotation and speed values to zero
	position.zero();
	rotation.zero();
	speed.zero();
 
	// How fast we move (higher values mean we move and strafe faster)
	movementSpeedFactor = 100.0;
 
	pitchSensitivity = 0.2; // How sensitive mouse movements affect looking up and down
	yawSensitivity   = 0.2; // How sensitive mouse movements affect looking left and right
 
	// To begin with, we aren't holding down any keys
	holdingForward     = false;
	holdingBackward    = false;
	holdingLeftStrafe  = false;
	holdingRightStrafe = false;
}
 
// Function to convert degrees to radians
const double Camera::toRads(const double &theAngleInDegrees) const
{
	return theAngleInDegrees * TO_RADS;
}
 
// Function to deal with mouse position changes
void Camera::handleMouseMove(int mouseX, int mouseY)
{
	// Calculate our horizontal and vertical mouse movement from middle of the window
	double horizMovement = (mouseX - windowMidX+1) * yawSensitivity;
	double vertMovement  = (mouseY - windowMidY) * pitchSensitivity;
 
	std::cout << "Mid window values: " << windowMidX << "\t" << windowMidY << std::endl;
	std::cout << "Mouse values     : " << mouseX << "\t" << mouseY << std::endl;
	std::cout << horizMovement << "\t" << vertMovement << std::endl << std::endl;
 
	// Apply the mouse movement to our rotation vector. The vertical (look up and down)
	// movement is applied on the X axis, and the horizontal (look left and right)
	// movement is applied on the Y Axis
	rotation.addX(vertMovement);
	rotation.addY(horizMovement);
 
	// Limit loking up to vertically up
	if (rotation.getX() < -90)
	{
		rotation.setX(-90);
	}
 
	// Limit looking down to vertically down
	if (rotation.getX() > 90)
	{
		rotation.setX(90);
	}
 
	// If you prefer to keep the angles in the range -180 to +180 use this code
	// and comment out the 0 to 360 code below.
	//
	// Looking left and right. Keep the angles in the range -180.0f (anticlockwise turn looking behind) to 180.0f (clockwise turn looking behind)
	/*if (yRot < -180.0f)
	{
	    yRot += 360.0f;
	}
 
	if (yRot > 180.0f)
	{
	    yRot -= 360.0f;
	}*/
 
	// Looking left and right - keep angles in the range 0.0 to 360.0
	// 0 degrees is looking directly down the negative Z axis "North", 90 degrees is "East", 180 degrees is "South", 270 degrees is "West"
	// We can also do this so that our 360 degrees goes -180 through +180 and it works the same, but it's probably best to keep our
	// range to 0 through 360 instead of -180 through +180.
	if (rotation.getY() < 0)
	{
		rotation.addY(360);
	}
	if (rotation.getY() > 360)
	{
		rotation.addY(-360);
	}
 
	// Reset the mouse position to the centre of the window each frame
	glfwSetMousePos(windowMidX, windowMidY);
}
 
// Function to calculate which direction we need to move the camera and by what amount
void Camera::move(double deltaTime)
{
	// Vector to break up our movement into components along the X, Y and Z axis
	Vec3<double> movement;
 
	// Get the sine and cosine of our X and Y axis rotation
	double sinXRot = sin( toRads( rotation.getX() ) );
	double cosXRot = cos( toRads( rotation.getX() ) );
 
	double sinYRot = sin( toRads( rotation.getY() ) );
	double cosYRot = cos( toRads( rotation.getY() ) );
 
	double pitchLimitFactor = cosXRot; // This cancels out moving on the Z axis when we're looking up or down
 
	if (holdingForward)
	{
		movement.addX(sinYRot * pitchLimitFactor);
		movement.addY(-sinXRot);
		movement.addZ(-cosYRot * pitchLimitFactor);
	}
 
	if (holdingBackward)
	{
		movement.addX(-sinYRot * pitchLimitFactor);
		movement.addY(sinXRot);
		movement.addZ(cosYRot * pitchLimitFactor);
	}
 
	if (holdingLeftStrafe)
	{
		movement.addX(-cosYRot);
		movement.addZ(-sinYRot);
	}
 
	if (holdingRightStrafe)
	{
		movement.addX(cosYRot);
		movement.addZ(sinYRot);
	}
 
	// Normalise our movement vector
	movement.normalise();
 
	// Calculate our value to keep the movement the same speed regardless of the framerate...
	double framerateIndependentFactor = movementSpeedFactor * deltaTime;
 
	// .. and then apply it to our movement vector.
	movement *= framerateIndependentFactor;
 
	// Finally, apply the movement to our position
	position += movement;
}

Rather than me explaining each individual piece of how to fit it together, here’s a worked example – it’s really quite easy to use:

#include <iostream>
#include <string>
 
#include <GL/glfw.h>      // Include OpenGL Framework library
 
#include "Camera.h"       // Include our Camera header so we can work with Camera objects
#include "FpsManager.hpp" // Include our FpsManager class
#include "Vec3.hpp"       // Include our Vec3 class
 
// Specify default namespace for commonly used elements
using std::string;
using std::cout;
using std::endl;
 
// Define a few constants for error conditions
const int GLFW_INIT_ERROR   = -1;
const int GLFW_WINDOW_ERROR = -2;
 
// Define a pointer to our camera object
Camera *cam;
 
// Define our window title to append the FPS stats to
string windowTitle = "FPS Controls Refactored | r3dux | Dec 2012";
 
// Create a FPS manager that locks to 60fps and updates the window title with stats every 3 seconds
FpsManager fpsManager(60.0, 3.0, windowTitle);
 
GLint windowWidth   = 800;              // Width of our window
GLint windowHeight  = 600;              // Heightof our window
 
GLint midWindowX    = windowWidth  / 2; // Middle of the window horizontally
GLint midWindowY    = windowHeight / 2; // Middle of the window vertically
 
GLfloat fieldOfView = 45.0f;            // Define our field of view (i.e. how quickly foreshortening occurs)
GLfloat near        = 2.0f;             // The near (Z Axis) point of our viewing frustum (default 2.0f)
GLfloat far         = 1500.0f;          // The far  (Z Axis) point of our viewing frustum (default 1500.0f)
 
// Callback function to handle keypresses
void handleKeypress(int theKey, int theAction)
{
	// If a key is pressed, toggle the relevant key-press flag
	if (theAction == GLFW_PRESS)
	{
		switch (theKey)
		{
		case 'W':
			cam->holdingForward = true;
			break;
		case 'S':
			cam->holdingBackward = true;
			break;
		case 'A':
			cam->holdingLeftStrafe = true;
			break;
		case 'D':
			cam->holdingRightStrafe = true;
			break;
		case '[':
			fpsManager.setTargetFps(fpsManager.getTargetFps() - 10);
			break;
		case ']':
			fpsManager.setTargetFps(fpsManager.getTargetFps() + 10);
			break;
		default:
			// Do nothing...
			break;
		}
	}
	else // If a key is released, toggle the relevant key-release flag
	{
		switch (theKey)
		{
		case 'W':
			cam->holdingForward = false;
			break;
		case 'S':
			cam->holdingBackward = false;
			break;
		case 'A':
			cam->holdingLeftStrafe = false;
			break;
		case 'D':
			cam->holdingRightStrafe = false;
			break;
		default:
			// Do nothing...
			break;
		}
	}
}
 
// Callback function to handle mouse movements
void handleMouseMove(int mouseX, int mouseY)
{
	cam->handleMouseMove(mouseX, mouseY);
}
 
void initGL()
{
	// ----- GLFW Settings -----
 
	glfwDisable(GLFW_MOUSE_CURSOR); // Hide the mouse cursor
 
	glfwSwapInterval(0);            // Disable vsync
 
	// ----- Window and Projection Settings -----
 
	// Set the window title
	glfwSetWindowTitle("Solar System FPS Controls Mk2| r3dux.org | Dec 2012");
 
	// Setup our viewport to be the entire size of the window
	glViewport(0, 0, (GLsizei)windowWidth, (GLsizei)windowHeight);
 
	// Change to the projection matrix, reset the matrix and set up our projection
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
 
	// The following code is a fancy bit of math that is eqivilant to calling:
	// gluPerspective(fieldOfView / 2.0f, width / height, near, far);
	// We do it this way simply to avoid requiring glu.h
	GLfloat aspectRatio = (windowWidth > windowHeight)? float(windowWidth)/float(windowHeight) : float(windowHeight)/float(windowWidth);
	GLfloat fH = tan( float(fieldOfView / 360.0f * 3.14159f) ) * near;
	GLfloat fW = fH * aspectRatio;
	glFrustum(-fW, fW, -fH, fH, near, far);
 
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
 
	// ----- OpenGL settings -----
 
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);              // Set out clear colour to black, full alpha
	glEnable(GL_DEPTH_TEST);                           // Enable the depth buffer
	glClearDepth(1.0f);                                // Clear the entire depth of the depth buffer
	glDepthFunc(GL_LEQUAL);		                       // Set our depth function to overwrite if new value less than or equal to current value
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Ask for nicest perspective correction
	glLineWidth(2.0f);			                       // Set a 'chunky' line width
}
 
// Function to draw a grid of lines
void drawGround(float groundLevel)
{
	GLfloat extent      = 600.0f; // How far on the Z-Axis and X-Axis the ground extends
	GLfloat stepSize    = 20.0f;  // The size of the separation between points
 
	// Set colour to white
	glColor3ub(255, 255, 255);
 
	// Draw our ground grid
	glBegin(GL_LINES);
	for (GLint loop = -extent; loop < extent; loop += stepSize)
	{
		// Draw lines along Z-Axis
		glVertex3f(loop, groundLevel,  extent);
		glVertex3f(loop, groundLevel, -extent);
 
		// Draw lines across X-Axis
		glVertex3f(-extent, groundLevel, loop);
		glVertex3f( extent, groundLevel, loop);
	}
	glEnd();
}
 
// Function to draw our scene
void drawScene()
{
	// Clear the screen and depth buffer
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
	// Reset the matrix
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
 
	// Move the camera to our location in space
	glRotatef(cam->getXRot(), 1.0f, 0.0f, 0.0f); // Rotate our camera on the x-axis (looking up and down)
	glRotatef(cam->getYRot(), 0.0f, 1.0f, 0.0f); // Rotate our camera on the  y-axis (looking left and right)
 
	// Translate the ModelView matrix to the position of our camera - everything should now be drawn relative
	// to this position!
	glTranslatef( -cam->getXPos(), -cam->getYPos(), -cam->getZPos() );
 
	drawGround(-100.0f); // Draw lower ground grid
	drawGround(100.0f);  // Draw upper ground grid
 
	// ----- Stop Drawing Stuff! ------
 
	glfwSwapBuffers(); // Swap the buffers to display the scene (so we don't have to watch it being drawn!)
}
 
// Fire it up...
int main(int argc, char **argv)
{
	cout << "Controls: Use WSAD and the mouse to move around!" << endl;
 
	// Frame counter and window settings variables
	int redBits    = 8, greenBits = 8,    blueBits    = 8;
	int alphaBits  = 8, depthBits = 24,   stencilBits = 0;
 
	// Flag to keep our main loop running
	bool running = true;
 
	// ----- Intialiase GLFW -----
 
	// Initialise GLFW
	if (!glfwInit() )
	{
		std::cout << "Failed to initialise GLFW!" << endl;
		glfwTerminate();
		return GLFW_INIT_ERROR;
	}
 
	// Create a window
	if( !glfwOpenWindow(windowWidth, windowHeight, redBits, greenBits, blueBits, alphaBits, depthBits, stencilBits, GLFW_WINDOW))
	{
		std::cout << "Failed to open window!" << std::endl;
		glfwTerminate();
		return GLFW_WINDOW_ERROR;
	}
 
	// Call our initGL function to set up our OpenGL options
	initGL();
 
	// Instantiate our pointer to a Camera object providing it the size of the window
	cam = new Camera(windowWidth, windowHeight);
 
	// Set the mouse cursor to the centre of our window
	glfwSetMousePos(midWindowX, midWindowY);
 
	// Specify the function which should execute when a key is pressed or released
	glfwSetKeyCallback(handleKeypress);
 
	// Specify the function which should execute when the mouse is moved
	glfwSetMousePosCallback(handleMouseMove);
 
	// The deltaTime variable keeps track of how much time has elapsed between one frame and the next.
	// This allows us to perform framerate independent movement i.e. the camera will move at the same
	// overall speed regardless of whether the app's running at (for example) 6fps, 60fps or 600fps!
	double deltaTime = 0.0;
 
	while (running)
	{
		// Calculate our camera movement
		cam->move(deltaTime);
 
		// Draw our scene
		drawScene();
 
		// exit if ESC was pressed or window was closed
		running = !glfwGetKey(GLFW_KEY_ESC) && glfwGetWindowParam(GLFW_OPENED);
 
		// Call our fpsManager to limit the FPS and get the frame duration to pass to the cam->move method
		deltaTime = fpsManager.enforceFPS();
	}
 
	// Clean up GLFW and exit
	glfwTerminate();
 
	delete cam; // Delete our pointer to the camera object
 
	return 0;
}

Finally! Done! You can see a video of the first version of the FPS controls here – this code works identically, it’s just that the Camera is now in its own class, we’re using our own little Vec3 class to keep group and manipulate some values, and the whole thing works in a framerate independent manner thanks to the FpsManager class. Phew!

Cheers!

FpsManager – A C++ helper class for framerate independent movement

Update – September 2013: Fixed an issue whereby the enforceFPS function only returned the time it took to run the enforceFPS function itself because I forgot to add the frameDuration. Fixed another issue where I reset the frameCount to 1 when it should have been reset to 0. Oops…


I wrote only a few months back that I didn’t want to write another piece of FPS code, ever. But this was before I started taking framerate independent movement seriously. In my past coding I’ve just enabled VSync and been done with it – as long as the machine had enough processing capacity to perform at 60fps everything was fine, and nearly everything I wrote was so simple that it didn’t task the box too much.

However, as I’ve been working on a lot of Android code recently where the processing capacity of the device can easily vary by orders of magnitude, I’ve started thinking more that I really need to be able to cater to framerate changes gracefully. And for this, I’ve reinvented the wheel – if only to be absolutely sure in no uncertain terms about how the wheel frickn’ works.

So to put this to the test, I wrote the FpsManager class, and rewrote the camera from my old post on Simple OpenGL FPS Controls into a proper class suitable for reuse in multiple projects and capable of working in a framerate independent manner. That’s the next post…

…first things first: The FpsManager! ;-)

FpsManager.hpp

  1. #include <string>
  2. #include <sstream>
  3.  
  4. #ifndef __glfw_h_
  5.     #include <GL/glfw.h> // We need GLFW for this, so let's check for it- although it'd be a doddle to convert to non-GLFW using code.
  6. #endif
  7.  
  8. /** The FpsManager class is designed to work with GLFW and enforces a specified framerate on an application.
  9.   * It can also display the current framerate at user-specified intervals, and in addition returns the time
  10.   * duration since the last frame, which can be used to implement framerate independent movement.
  11.   *
  12.   * Author: r3dux
  13.   * Revision: 0.3
  14.   * Date: 1st September 2013
  15.   *
  16.   * ---- Creation examples (it's most useful to create your fpsManager object globally in your Main.cpp file): ----
  17.   *
  18.   *     FpsManager fpsManager(60.0);                    // Lock to 60fps, no reporting of framerate
  19.   *
  20.   *     FpsManager fpsManager(85.0, 3.0);               // Lock to 85fps, output FPS to console once every three seconds
  21.   *
  22.   *     FpsManager fpsManager(30.0, 0.5, "My App");     // Lock to 30fps, output FPS to console & window title every half second
  23.   *
  24.   *
  25.   * ---- Using the fpsManager in your main loop: ----
  26.   *
  27.   * bool running     = true;
  28.   * double deltaTime = 0.0;
  29.   *
  30.   * while (running)
  31.   * {
  32.   *     // Calculate our camera movement
  33.   *     cam->move(deltaTime);
  34.   *
  35.   *     // Draw our scene
  36.   *     drawScene();
  37.   *
  38.   *     // Exit if ESC was pressed or window was closed
  39.   *     running = !glfwGetKey(GLFW_KEY_ESC) && glfwGetWindowParam(GLFW_OPENED);
  40.   *
  41.   *     // Call our fpsManager to limit the FPS and get the frame duration to pass to the cam->move method
  42.   *     deltaTime = fpsManager.enforceFPS();
  43.   * }
  44.   *
  45.   * That's it! =D
  46.   */
  47.  
  48. class FpsManager
  49. {
  50.  
  51.     private:
  52.         double frameStartTime;         // Frame start time
  53.         double frameEndTime;           // Frame end time
  54.         double frameDuration;          // How many milliseconds between the last frame and this frame
  55.  
  56.         double targetFps;              // The desired FPS to run at (i.e. maxFPS)
  57.         double currentFps;             // The current FPS value
  58.         int    frameCount;             // How many frames have been drawn s
  59.  
  60.         double targetFrameDuration;    // How many milliseconds each frame should take to hit a target FPS value (i.e. 60fps = 1.0 / 60 = 0.016ms)
  61.         double sleepDuration;          // How long to sleep if we're exceeding the target frame rate duration
  62.  
  63.         double lastReportTime;         // The timestamp of when we last reported
  64.         double reportInterval;         // How often to update the FPS value
  65.  
  66.         std::string windowTitle;       // Window title to update view GLFW
  67.  
  68.         bool verbose;                  // Whether or not to output FPS details to the console or update the window
  69.  
  70.         // Limit the minimum and maximum target FPS value to relatively sane values
  71.         static const double MIN_TARGET_FPS = 20.0;
  72.         static const double MAX_TARGET_FPS = 60.0; // If you set this above the refresh of your monitor and enable VSync it'll break! Be aware!
  73.  
  74.         // Private method to set relatively sane defaults. Called by constructors before overwriting with more specific values as required.
  75.         void init(double theTargetFps, bool theVerboseSetting)
  76.         {
  77.             setTargetFps(theTargetFps);
  78.  
  79.             frameCount     = 0;
  80.             currentFps     = 0.0;
  81.             sleepDuration  = 0.0;
  82.             frameStartTime = glfwGetTime();
  83.             frameEndTime   = frameStartTime + 1;
  84.             frameDuration  = 1;
  85.             lastReportTime = frameStartTime;
  86.             reportInterval = 1.0f;
  87.             windowTitle    = "NONE";
  88.             verbose        = theVerboseSetting;
  89.         }
  90.  
  91.     public:
  92.  
  93.         // Single parameter constructor - just set a desired framerate and let it go.
  94.         // Note: No FPS reporting by default, although you can turn it on or off later with the setVerbose(true/false) method
  95.         FpsManager(int theTargetFps)
  96.         {
  97.             init(theTargetFps, false);
  98.         }
  99.  
  100.         // Two parameter constructor which sets a desired framerate and a reporting interval in seconds
  101.         FpsManager(int theTargetFps, double theReportInterval)
  102.         {
  103.             init(theTargetFps, true);
  104.  
  105.             setReportInterval(theReportInterval);
  106.         }
  107.  
  108.         // Three parameter constructor which sets a desired framerate, how often to report, and the window title to append the FPS to
  109.         FpsManager(int theTargetFps, float theReportInterval, std::string theWindowTitle)
  110.         {
  111.             init(theTargetFps, true); // If you specify a window title it's safe to say you want the FPS to update there ;)
  112.  
  113.             setReportInterval(theReportInterval);
  114.  
  115.             windowTitle = theWindowTitle;
  116.         }
  117.  
  118.         // Getter and setter for the verbose property
  119.         bool getVerbose()
  120.         {
  121.             return verbose;
  122.         }
  123.         void setVerbose(bool theVerboseValue)
  124.         {
  125.             verbose = theVerboseValue;
  126.         }
  127.  
  128.         // Getter and setter for the targetFps property
  129.         int getTargetFps()
  130.         {
  131.             return targetFps;
  132.         }
  133.  
  134.         void setTargetFps(int theFpsLimit)
  135.         {
  136.             // Make at least some attempt to sanitise the target FPS...
  137.             if (theFpsLimit < MIN_TARGET_FPS)
  138.             {
  139.                 theFpsLimit = MIN_TARGET_FPS;
  140.                 std::cout << "Limiting FPS rate to legal minimum of " << MIN_TARGET_FPS << " frames per second." << std::endl;
  141.             }
  142.             if (theFpsLimit > MAX_TARGET_FPS)
  143.             {
  144.                 theFpsLimit = MAX_TARGET_FPS;
  145.                 std::cout << "Limiting FPS rate to legal maximum of " << MAX_TARGET_FPS << " frames per second." << std::endl;
  146.             }
  147.  
  148.             // ...then set it and calculate the target duration of each frame at this framerate
  149.             targetFps = theFpsLimit;
  150.             targetFrameDuration = 1.0 / targetFps;
  151.         }
  152.  
  153.         double getFrameDuration() { return frameDuration; } // Returns the time it took to complete the last frame in milliseconds
  154.  
  155.         // Setter for the report interval (how often the FPS is reported) - santises input.
  156.         void setReportInterval(float theReportInterval)
  157.         {
  158.             // Ensure the time interval between FPS checks is sane (low cap = 0.1s, high-cap = 10.0s)
  159.             // Negative numbers are invalid, 10 fps checks per second at most, 1 every 10 secs at least.
  160.             if (theReportInterval < 0.1)
  161.             {
  162.                 theReportInterval = 0.1;
  163.             }
  164.             if (theReportInterval > 10.0)
  165.             {
  166.                 theReportInterval = 10.0;
  167.             }
  168.             reportInterval = theReportInterval;
  169.         }
  170.  
  171.         // Method to force our application to stick to a given frame rate and return how long it took to process a frame
  172.         double enforceFPS()
  173.         {
  174.             // Get the current time
  175.             frameEndTime = glfwGetTime();
  176.  
  177.             // Calculate how long it's been since the frameStartTime was set (at the end of this method)
  178.             frameDuration = frameEndTime - frameStartTime;
  179.  
  180.             if (reportInterval != 0.0f)
  181.             {
  182.  
  183.                 // Calculate and display the FPS every specified time interval
  184.                 if ((frameEndTime - lastReportTime) > reportInterval)
  185.                 {
  186.                     // Update the last report time to be now
  187.                     lastReportTime = frameEndTime;
  188.  
  189.                     // Calculate the FPS as the number of frames divided by the interval in seconds
  190.                     currentFps =  (double)frameCount / reportInterval;
  191.  
  192.                     // Reset the frame counter to 1 (and not zero - which would make our FPS values off)
  193.                     frameCount = 1;
  194.  
  195.                     if (verbose)
  196.                     {
  197.                         std::cout << "FPS: " << currentFps << std::endl;
  198.  
  199.                         // If the user specified a window title to append the FPS value to...
  200.                         if (windowTitle != "NONE")
  201.                         {
  202.                             // Convert the fps value into a string using an output stringstream
  203.                             std::ostringstream stream;
  204.                             stream << currentFps;
  205.                             std::string fpsString = stream.str();
  206.  
  207.                             // Append the FPS value to the window title details
  208.                             std::string tempWindowTitle = windowTitle + " | FPS: " + fpsString;
  209.  
  210.                             // Convert the new window title to a c_str and set it
  211.                             const char* pszConstString = tempWindowTitle.c_str();
  212.                             glfwSetWindowTitle(pszConstString);
  213.                         }
  214.  
  215.                     } // End of if verbose section
  216.  
  217.                 }
  218.                 else // FPS calculation time interval hasn't elapsed yet? Simply increment the FPS frame counter
  219.                 {
  220.                     ++frameCount;
  221.                 }
  222.  
  223.             } // End of if we specified a report interval section
  224.  
  225.             // Calculate how long we should sleep for to stick to our target frame rate
  226.             sleepDuration = targetFrameDuration - frameDuration;
  227.  
  228.             // If we're running faster than our target duration, sleep until we catch up!
  229.             if (sleepDuration > 0.0)
  230.                 glfwSleep(targetFrameDuration - frameDuration);
  231.  
  232.             // Reset the frame start time to be now - this means we only need put a single call into the main loop
  233.             frameStartTime = glfwGetTime();
  234.  
  235.             // Pass back our total frame duration (including any sleep and the time it took to run this function) to be used as our deltaTime value
  236.             return frameDuration + (frameStartTime - frameEndTime);
  237.  
  238.         } // End of our enforceFPS method
  239.  
  240. };

Comments? Suggestions? Think I’ve designed it badly, or quite well? Know why it works just fine (from a usability standpoint) but provides a framerate just under that requested?

Feel free to let me know in the comments below! Cheers! =D

A Simple GLFW FPS Counter

Update – September 2013: Need something that enforces a given frame rate rather than just reports the current frame rate? Try this: https://r3dux.org/2012/12/fpsmanager-a-c-helper-class-for-framerate-independent-movement/


I’m fed up of solving the same problem over and over again, so the next time I need some FPS measurements, I’m going to use this…

The Include Requirements

#include <iostream>
#include <string>
#include <sstream>
#include <GL/glfw.h>

The Function

double calcFPS(double theTimeInterval = 1.0, std::string theWindowTitle = "NONE")
{
	// Static values which only get initialised the first time the function runs
	static double t0Value       = glfwGetTime(); // Set the initial time to now
	static int    fpsFrameCount = 0;             // Set the initial FPS frame count to 0
	static double fps           = 0.0;           // Set the initial FPS value to 0.0
 
	// Get the current time in seconds since the program started (non-static, so executed every time)
	double currentTime = glfwGetTime();
 
	// Ensure the time interval between FPS checks is sane (low cap = 0.1s, high-cap = 10.0s)
	// Negative numbers are invalid, 10 fps checks per second at most, 1 every 10 secs at least.
	if (theTimeInterval < 0.1)
	{
		theTimeInterval = 0.1;
	}
	if (theTimeInterval > 10.0)
	{
		theTimeInterval = 10.0;
	}
 
	// Calculate and display the FPS every specified time interval
	if ((currentTime - t0Value) > theTimeInterval)
	{
		// Calculate the FPS as the number of frames divided by the interval in seconds
		fps = (double)fpsFrameCount / (currentTime - t0Value);
 
		// If the user specified a window title to append the FPS value to...
		if (theWindowTitle != "NONE")
		{
			// Convert the fps value into a string using an output stringstream
			std::ostringstream stream;
			stream << fps;
			std::string fpsString = stream.str();
 
			// Append the FPS value to the window title details
			theWindowTitle += " | FPS: " + fpsString;
 
			// Convert the new window title to a c_str and set it
			const char* pszConstString = theWindowTitle.c_str();
			glfwSetWindowTitle(pszConstString);
		}
		else // If the user didn't specify a window to append the FPS to then output the FPS to the console
		{
			std::cout << "FPS: " << fps << std::endl;
		}
 
		// Reset the FPS frame counter and set the initial time to be now
		fpsFrameCount = 0;
		t0Value = glfwGetTime();
	}
	else // FPS calculation time interval hasn't elapsed yet? Simply increment the FPS frame counter
	{
		fpsFrameCount++;
	}
 
	// Return the current FPS - doesn't have to be used if you don't want it!
	return fps;
}

Usage Examples

Call any of these in your main loop…

string windowTitle = "My Lovely App: "; // You might want to have string with window title hanging around...
 
cout << calcFPS() << endl;              // Print the FPS to the console once per second
cout << calcFPS(2.0) << endl;           // Print the FPS to the console every 2 seconds
calcFPS(1.0, windowTitle);              // Update the window title to include the FPS details once per second
calcFPS(2.0, windowTitle);              // Update the window title to include the FPS details every 2 seconds
calcFPS(3.0, "Current FPS: ");          // Update the window title to the string literal "Current FPS: " + the FPS details every 3 seconds

Suggestions?

I think that’s pretty usable and clean – if you’ve got any suggestions I’d really be interested in hearing them – I simply don’t want to re-implement a FPS counter in C++ ever again.

Simple texture loading with DevIL revisited

I wrote an article a while back about loading images to use as textures in OpenGL, and in it I’d written my own single line image to texture-handle function because I couldn’t get the built-in ilutGLLoadImage function to work. Since I’ve been getting blank looks and failure to compile reports about it, I thought I’d go and take a look at it again, and this time ilutGLLoadImage seems to work just fine, so let’s document it up properly shall we?

Pre-requisites

You will need:

  • A C++ compiler of your choice (I prefer Code::Blocks)
  • Working OpenGL drivers
  • A copy of DevIL
  • A copy of GLEW (GL Extension Wrangler – to make the functionality in your OpenGL drivers availble for use)
  • A copy of GLFW (GL FrameWork – to quickly and easily set up a OpenGL context)
  • An image to load

Preparation and libraries

Create a new project in your IDE of choice, and link in the following libraries:
DevIL required libraries

You’ll have to figure out where the libraries are on your own system but these are where they are on mine, where a /usr/local/ address indicates packages I’ve build myself, /usr/lib/ indicates standard system installed packages, and no prefix uses any paths defined by the compiler (in this case /usr/ where the lib and include directories are assumed). I’ve also added /usr/local/ to the include path so that any headers in /usr/local/include/ are picked up before any in /usr/include. Yes, library paths are a pain ;)

Also, be sure that your project includes its own directory as the working directory (i.e. the directory to be in when executing) so that you can place your image (in this example, a file called abstract-image.jpg) in the main project directory and it’ll be picked up when we try to load it. The way that you specify the working directory will vary depending on the IDE you’re using, but in Code::Blocks you set it from Project | Properties | Build targets like this:

Code::Blocks Working Directory

Source Code

Create a new project with source code something like this:

  1. #include <iostream>
  2. #include <cstdlib>
  3.  
  4. #include <GL/glew.h>		// Include GLEW, which pulls in OpenGL headers as required
  5. #include <GL/glfw.h>		// Include OpenGL Framework headers
  6.  
  7. #define ILUT_USE_OPENGL		// This MUST be defined before calling the DevIL headers or we don't get OpenGL functionality
  8. #include <IL/il.h>
  9. #include <IL/ilu.h>
  10. #include <IL/ilut.h>
  11.  
  12. GLuint textureHandle;           // Create a uint to store the handle to our texture
  13.  
  14. GLfloat frameCount = 0.0f;
  15.  
  16. void initGL(int width, int height)
  17. {
  18. 	//  ----- Initialise GLEW -----
  19. 	GLenum err = glewInit();
  20. 	if (GLEW_OK != err)
  21. 	{
  22. 		std::cout << "GLEW initialisation error: " << glewGetErrorString(err) << std::endl;
  23. 		exit(-1);
  24. 	}
  25. 	std::cout << "GLEW intialised successfully. Using GLEW version: " << glewGetString(GLEW_VERSION) << std::endl;
  26.  
  27. 	// ----- Window and Projection Settings -----
  28.  
  29. 	// Set the window title
  30. 	glfwSetWindowTitle("ilutGLLoadImage Test | September 2011 | r3dux.org");
  31.  
  32. 	// Setup our viewport to be the entire size of the window
  33. 	glViewport(0, 0, (GLsizei)width, (GLsizei)height);
  34.  
  35. 	GLfloat ratio = (float)width / (float)height;	// Calculate the window ratio
  36.  
  37. 	// Change to the projection matrix, reset the matrix and set up our projection
  38. 	glMatrixMode(GL_PROJECTION);
  39. 	glLoadIdentity();
  40. 	gluPerspective(45.0f, ratio, 0.1f, 100.0f); // Params: Field-of-Vision, window-ration, near-clip-plane, far-clip-plane
  41.  
  42. 	// ----- OpenGL settings -----
  43.  
  44. 	glfwSwapInterval(1);                               // Lock to vertical sync of monitor (normally 60Hz, so 60fps)
  45. 	glEnable(GL_DEPTH);                                // Enable the depth buffer
  46. 	glDepthFunc(GL_LEQUAL);                            // Set our depth function to overwrite if new value less than or equal to current value
  47. 	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Ask for nicest perspective correction
  48. 	glEnable(GL_TEXTURE_2D);                           // Enable 2D textures
  49.  
  50. 	// ----- Initialize DevIL libraries -----
  51.  
  52. 	// DevIL sanity check
  53. 	if ( (iluGetInteger(IL_VERSION_NUM) < IL_VERSION) || (iluGetInteger(ILU_VERSION_NUM) < ILU_VERSION) || (ilutGetInteger(ILUT_VERSION_NUM) < ILUT_VERSION) )
  54. 	{
  55. 		std::cout << "DevIL versions are different... Exiting." << std::endl;
  56. 		exit(-1);
  57. 	}
  58.  
  59. 	// Initialise all DevIL functionality
  60. 	ilInit();
  61. 	iluInit();
  62. 	ilutInit();
  63. 	ilutRenderer(ILUT_OPENGL);	// Tell DevIL that we're using OpenGL for our rendering
  64. }
  65.  
  66. void drawScene()
  67. {
  68. 	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear the screen and depth buffer
  69.  
  70. 	// Reset the matrix to identity
  71. 	glMatrixMode(GL_MODELVIEW);
  72. 	glLoadIdentity();
  73.  
  74. 	glTranslatef(0.0f, 0.0f, -40.0f); // Move things back into the screen
  75.  
  76. 	glRotatef(frameCount, 0.1f, 1.0f, 0.3f); // Rotate the MVP matrix orientation
  77.  
  78. 	glBindTexture(GL_TEXTURE_2D, textureHandle); // Select the texture to use and bind to it
  79.  
  80. 	static float hsize = 15.0f; // Vertical size of the quad
  81. 	static float vsize = 10.0f; // Vertical size of the quad
  82.  
  83. 	// Draw our textured geometry (just a rectangle in this instance)
  84. 	glEnable(GL_TEXTURE_2D);
  85. 	glBegin(GL_QUADS);
  86. 		glTexCoord2f(0.0, 0.0); glVertex3f(-hsize, -vsize, 0.0f);  // Top left
  87. 		glTexCoord2f(0.0, 1.0);	glVertex3f(-hsize,  vsize, 0.0f);  // Bottom left
  88. 		glTexCoord2f(1.0, 1.0);	glVertex3f(hsize,   vsize, 0.0f);  // Bottom right
  89. 		glTexCoord2f(1.0, 0.0);	glVertex3f(hsize,  -vsize, 0.0f);  // Top right
  90. 	glEnd();
  91. 	glDisable(GL_TEXTURE_2D);
  92.  
  93. 	// ----- Stop Drawing Stuff! ------
  94.  
  95. 	glfwSwapBuffers(); // Swap the buffers to display the scene (so we don't have to watch it being drawn!)
  96. 	frameCount++;
  97. }
  98.  
  99. int main()
  100. {
  101. 	// Window settings
  102. 	int width      = 800, height   = 600;
  103. 	int redBits    = 8,   greenBits = 8,  blueBits    = 8;
  104. 	int alphaBits  = 8,   depthBits = 24, stencilBits = 8;
  105.  
  106. 	// Flag to keep our main loop running
  107. 	bool running = true;
  108.  
  109. 	// Initialise glfw
  110. 	glfwInit();
  111.  
  112. 	// Create a window
  113. 	if(!glfwOpenWindow(width, height, redBits, greenBits, blueBits, alphaBits, depthBits, stencilBits, GLFW_WINDOW))
  114. 	{
  115. 		std::cout << "Failed to open window!" << std::endl;
  116. 		glfwTerminate();
  117. 		return 0;
  118. 	}
  119.  
  120. 	initGL(width, height); 	                        // Call our initGL function to set up our OpenGL options
  121.  
  122. 	ILstring imageFilename = "abstract-image.jpg";  // Specify filename
  123.  
  124. 	textureHandle = ilutGLLoadImage(imageFilename); // Load image directly to texture
  125.  
  126. 	// Output last image loaded properties
  127. 	// Available properties list is at: http://www-f9.ijs.si/~matevz/docs/DevIL/il/f00027.htm
  128. 	std::cout << "Image width         : " << ilGetInteger(IL_IMAGE_WIDTH)          << std::endl;
  129. 	std::cout << "Image height        : " << ilGetInteger(IL_IMAGE_HEIGHT)         << std::endl;
  130. 	std::cout << "Image bits per pixel: " << ilGetInteger(IL_IMAGE_BITS_PER_PIXEL) << std::endl;
  131.  
  132. 	// Main loop
  133. 	while (running == true)
  134. 	{
  135. 		// Draw our scene
  136. 		drawScene();
  137.  
  138. 		// exit if ESC was pressed or window was closed
  139. 		running = !glfwGetKey(GLFW_KEY_ESC) && glfwGetWindowParam( GLFW_OPENED);
  140. 	}
  141.  
  142. 	glfwTerminate();
  143.  
  144. 	return 0;
  145. }

End Result

If you’ve linked in the libraries and used source code like the above, you should end up with a working texture that looks something like this:
ilutGLLoadImage Example

And information regarding the image being used that looks something like this:
DevIL Image Data

I’ve attached a copy of my Code::Blocks project here for anyone who wants it.

Cheers!

Credits: The wallpaper I used as the texture in this guide is Life by N.Design Studio, which you can find here.