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 OpenGL Keyboard and Mouse FPS Controls

Note: This was written in January 2011 – I just never posted it, but I’d already uploaded the video to YouTube and someone asked for the code, so here it is, in all its fixed-pipeline glory ;)

Update – September 2013: I took these camera controls and wrapped them up into a Camera class in a later post which you can find here: https://r3dux.org/2012/12/a-c-camera-class-for-simple-opengl-fps-controls/. When I did this I wasn’t used to GLM (the OpenGL Mathematics library) so I just rolled my own Vec3 class – you can happily substitute glm::vec3’s if you’d like, and in fact I’d recommend it. Cheers!


I’m working on my OpenGL skills (or lack thereof) at the moment, and wanted to implement some 3D movement controls kinda of like a FPS with clipping off, so I read some chapters of the hallowed OpenGL SuperBible and did some googling, where I came across Swiftless‘ camera tutorials (Part 1, Part 2, Part 3) which gave me a really good start (Thank you, Swiftless!) on how to manipulate the ModelView matrix so we can move around a 3D scene, only it wasn’t quite perfect…

Strange things would happen like you’d look vertically downwards (i.e. directly down the negative Y axis), then you’d push forward – and yeah, you’d move “down”, but you’d also move “forward” at the same time (oh, and I’m putting things like “down” and “forward” in quotes because these concepts are all relative to your viewing orientation – not because I’m trying to be “sarcastic” or anything =P)

Anyways, I had a play with it and sorted it out after spending some time looking at the graphs for trigonometric functions and doing a little bit of off-setting and range-limiting as required. Check it out:

It actually looks quite a lot better running live than in the video due to mis-matched frame-capture rates and the like, but you get the idea =D

Full source code is available after the jump.

Cheers!

Continue reading Simple OpenGL Keyboard and Mouse FPS Controls