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!

Vec3: A Simple Vector Class in C++

This is really the second post of three updating an earlier post on how to write some simple FPS-type controls for OpenGL – last post we looked at a FpsManager class so we could get frame timings to implement framerate independent movement. This time, we’re looking at a simple Vec3 class which stores 3D coordinates and helps to perform some operations on them. This is going to form part of the Camera class in the final part, but I thought I’d put it here on its own so it can be re-used.

There’s probably about ten million different vector classes out there, but I really wanted to write my own so I understood exactly how it worked, and I’ve commented it pretty heavily in the process – so hopefully if you’re looking for a vector class you might be able to look at this one, see how it works and what it does, and take and modify it for your own work.

Before skipping to the code, let’s just take a quick look at what it does – it packages up 3 values called x, y and z, and allows us to easily manipulate them. Quite what you store in these values is up to you, it could be a vertex position, or a direction vector, or a RGB colour – anything that has three values, really.

If we were dealing with two vertexes, we can do stuff like this:

    // Define two initial vectors
    Vec3<double> vector1(1, 2, 3); // A coordinate +1 on the X axis (horiz), +2 on the Y axis (vert), and +3 on the Z axis (depth)
    Vec3<double> vector2(4, 5, 6); // A coordinate +4 on the X axis (horiz), +5 on the Y axis (vert), and +6 on the Z axis (depth)
 
    // Create a new vector which is the sum of these two vectors added together
    Vec3<double>result = vector1 + vector2;
    result.display();           // (5, 7, 9)
 
    // Add the first vector to our result vector
    result += vector1;
    result.display();           // 6, 9, 12
 
    // Subtract the second vector from our result vector
    result -= vector2;
    result.display();           // 2, 4, 6
 
    // Divide our result vector by the scalar value 2
    result /= 2;
    result.display();           // 1, 2, 3
 
    // Multiply both vectors together and assign to our result vector (this is a dot-product
    // operation, but we have specific dot product functions we can use, too)
    result = vector1 * vector2;
    result.display();           // 4, 10, 18
 
    // Multiply our result vector by 2
    result *= 2;
    result.display();           // 8, 20, 36
 
    // Normalise our result vector so that all values fall within the range -1 to +1
    result.normalise();
    result.display();           // 0.190693, 0.476731, 0.858116
 
     // Calculate the distance between two points in 3D space
    double distance = Vec3<double>::getDistance(vector1, vector2);
    cout << "Distance between points: " << distance << endl; // 5.19615
 
    // Dot products only work on normalised values (i.e. each x/y/z value in the vector must be in the range -1 to +1)
    // So remember to normalise your vectors before computing the dot product!
    vector1.normalise();
    vector2.normalise();
    double dotProduct = Vec3<double>::dotProduct(vector1, vector2);
    cout << "Dot product: " << dotProduct << endl;           // 0.974632
 
    // Define some vectors pointing up, down, left, and right
    Vec3<double> up(   0,  1, 0);
    Vec3<double> down( 0, -1, 0);
    Vec3<double> left(-1,  0, 0);
    Vec3<double> right(1,  0, 0);
 
    // ------------ Dot Product Tests ------------
 
    // The dot product of two vectors pointing the same direction is 1
    dotProduct = Vec3<double>::dotProduct(up, up);
    cout << "Dot product of up and up: " << dotProduct << endl;
 
    // The dot product of two vectors which are perpendicular to each other is is 0
    dotProduct = Vec3<double>::dotProduct(up, right);
    cout << "Dot product of up and right: " << dotProduct << endl;
 
    // The dot product of two vectors pointing in opposite directions is -1
    dotProduct = Vec3<double>::dotProduct(up, down);
    cout << "Dot product of up and down: " << dotProduct << endl;
 
    // ------------ Cross Product Tests ------------
 
    // The cross product of a vector is the vector which is perpendicular to the plane made
    // by the two vectors specified. Whether it points "up" or "down" depends on the
    // handedness of the coordinate system and/or the order of vectors provided.
 
    // Test 1
    Vec3<double> crossProduct = Vec3<double>::crossProduct(up, right);
 
    // x = 0, y = 0, z = -1 (i.e. the vector perpendicular to up and right points INTO the screen)
    crossProduct.display(); 
 
    // Test 2
    crossProduct = Vec3<double>::crossProduct(right, up);
 
    // x = 0, y = 0, z = 1 (i.e. the vector perpendicular to right and up points OUT from screen)
    crossProduct.display();

Seems pretty easy to work with to me…

Here’s the code for the Vec3 class itself as a templatised, header-only C++ .hpp file – just include the Vec3.hpp class and you’re good to go:

#ifndef VEC3_HPP
#define VEC3_HPP
 
#include <iostream>
 
template <class T> class Vec3
{
    private:
        // A Vec3 simply has three properties called x, y and z
        T x, y, z;
 
    public:
        // ------------ Constructors ------------
 
        // Default constructor
        Vec3() { x = y = z = 0; };
 
        // Three parameter constructor
        Vec3(T xValue, T yValue, T zValue)
        {
            x = xValue;
            y = yValue;
            z = zValue;
        }
 
        // ------------ Getters and setters ------------
 
        void set(const T &xValue, const T &yValue, const T &zValue)
        {
            x = xValue;
            y = yValue;
            z = zValue;
        }
 
        T getX() const { return x; }
        T getY() const { return y; }
        T getZ() const { return z; }
 
        void setX(const T &xValue) { x = xValue; }
        void setY(const T &yValue) { y = yValue; }
        void setZ(const T &zValue) { z = zValue; }
 
        // ------------ Helper methods ------------
 
        // Method to reset a vector to zero
        void zero()
        {
            x = y = z = 0;
        }
 
        // Method to normalise a vector
        void normalise()
        {
            // Calculate the magnitude of our vector
            T magnitude = sqrt((x * x) + (y * y) + (z * z));
 
            // As long as the magnitude isn't zero, divide each element by the magnitude
            // to get the normalised value between -1 and +1
            if (magnitude != 0)
            {
                x /= magnitude;
                y /= magnitude;
                z /= magnitude;
            }
        }
 
        // Static method to calculate and return the scalar dot product of two vectors
        //
        // Note: The dot product of two vectors tell us things about the angle between
        // the vectors. That is, it tells us if they are pointing in the same direction
        // (i.e. are they parallel? If so, the dot product will be 1), or if they're
        // perpendicular (i.e. at 90 degrees to each other) the dot product will be 0,
        // or if they're pointing in opposite directions then the dot product will be -1.
        //
        // Usage example: double foo = Vec3<double>::dotProduct(vectorA, vectorB);
        static T dotProduct(const Vec3 &vec1, const Vec3 &vec2)
        {
            return vec1.x * vec2.x + vec1.y * vec2.y + vec1.z * vec2.z;
        }
 
        // Non-static method to calculate and return the scalar dot product of this vector and another vector
        //
        // Usage example: double foo = vectorA.dotProduct(vectorB);
        T dotProduct(const Vec3 &vec) const
        {
            return x * vec.x + y * vec.y + z * vec.z;
        }
 
        // Static method to calculate and return a vector which is the cross product of two vectors
        //
        // Note: The cross product is simply a vector which is perpendicular to the plane formed by
        // the first two vectors. Think of a desk like the one your laptop or keyboard is sitting on.
        // If you put one pencil pointing directly away from you, and then another pencil pointing to the
        // right so they form a "L" shape, the vector perpendicular to the plane made by these two pencils
        // points directly upwards.
        //
        // Whether the vector is perpendicularly pointing "up" or "down" depends on the "handedness" of the
        // coordinate system that you're using.
        //
        // Further reading: http://en.wikipedia.org/wiki/Cross_product
        //
        // Usage example: Vec3<double> crossVect = Vec3<double>::crossProduct(vectorA, vectorB);
        static Vec3 crossProduct(const Vec3 &vec1, const Vec3 &vec2)
        {
            return Vec3(vec1.y * vec2.z - vec1.z * vec2.y, vec1.z * vec2.x - vec1.x * vec2.z, vec1.x * vec2.y - vec1.y * vec2.x);
        }
 
        // Easy adders
        void addX(T value) { x += value; }
        void addY(T value) { y += value; }
        void addZ(T value) { z += value; }
 
        // Method to return the distance between two vectors in 3D space
        //
        // Note: This is accurate, but not especially fast - depending on your needs you might
        // like to use the Manhattan Distance instead: http://en.wikipedia.org/wiki/Taxicab_geometry
        // There's a good discussion of it here: http://stackoverflow.com/questions/3693514/very-fast-3d-distance-check
        // The gist is, to find if we're within a given distance between two vectors you can use:
        //
        // bool within3DManhattanDistance(Vec3 c1, Vec3 c2, float distance)
        // {
        //      float dx = abs(c2.x - c1.x);
        //      if (dx > distance) return false; // too far in x direction
        //
        //      float dy = abs(c2.y - c1.y);
        //      if (dy > distance) return false; // too far in y direction
        //
        //      float dz = abs(c2.z - c1.z);
        //      if (dz > distance) return false; // too far in z direction
        //
        //      return true; // we're within the cube
        // }
        //
        // Or to just calculate the straight Manhattan distance you could use:
        //
        // float getManhattanDistance(Vec3 c1, Vec3 c2)
        // {
        //      float dx = abs(c2.x - c1.x);
        //      float dy = abs(c2.y - c1.y);
        //      float dz = abs(c2.z - c1.z);
        //      return dx+dy+dz;
        // }
        //
        static T getDistance(const Vec3 &v1, const Vec3 &v2)
        {
            T dx = v2.x - v1.x;
            T dy = v2.y - v1.y;
            T dz = v2.z - v1.z;
 
            return sqrt(dx * dx + dy * dy + dz * dz);
        }
 
        // Method to display the vector so you can easily check the values
        void display()
        {
            std::cout << "X: " << x << "\t Y: " << y << "\t Z: " << z << std::endl;
        }
 
        // ------------ Overloaded operators ------------
 
        // Overloaded addition operator to add Vec3s together
        Vec3 operator+(const Vec3 &vector) const
        {
            return Vec3<T>(x + vector.x, y + vector.y, z + vector.z);
        }
 
        // Overloaded add and asssign operator to add Vec3s together
        void operator+=(const Vec3 &vector)
        {
            x += vector.x;
            y += vector.y;
            z += vector.z;
        }
 
        // Overloaded subtraction operator to subtract a Vec3 from another Vec3
        Vec3 operator-(const Vec3 &vector) const
        {
            return Vec3<T>(x - vector.x, y - vector.y, z - vector.z);
        }
 
        // Overloaded subtract and asssign operator to subtract a Vec3 from another Vec3
        void operator-=(const Vec3 &vector)
        {
            x -= vector.x;
            y -= vector.y;
            z -= vector.z;
        }
 
        // Overloaded multiplication operator to multiply two Vec3s together
        Vec3 operator*(const Vec3 &vector) const
        {
            return Vec3<T>(x * vector.x, y * vector.y, z * vector.z);
        }
 
        // Overloaded multiply operator to multiply a vector by a scalar
        Vec3 operator*(const T &value) const
        {
            return Vec3<T>(x * value, y * value, z * value);
        }
 
        // Overloaded multiply and assign operator to multiply a vector by a scalar
        void operator*=(const T &value)
        {
            x *= value;
            y *= value;
            z *= value;
        }
 
        // Overloaded multiply operator to multiply a vector by a scalar
        Vec3 operator/(const T &value) const
        {
            return Vec3<T>(x / value, y / value, z / value);
        }
 
        // Overloaded multiply and assign operator to multiply a vector by a scalar
        void operator/=(const T &value)
        {
            x /= value;
            y /= value;
            z /= value;
        }
};
 
#endif

Next post, we’re going to be implementing a FPS-style camera class using this Vec3 class to move around a 3D scene – and because of all our hard work in getting this vector class together, it’s going to make moving the camera around a doddle =D

Cheers!

PS3 Motion Input

http://www.youtube.com/watch?v=w0puP8nrIU8

I’m really in two minds about this and Natal… but then when the Wii was first announced and you could see the Wiimotes for the first time it just screamed fad!, but once you do a little bowling or tennis is all sort of made sense… It was so new it just seemed wrong, but with time became commonplace.

On one hand, it might be cool to have true motion tracking, and I’m sure there could be some good uses for it – throwing grenades/punches, light sabres, manipulating object on screen – that sort of thing. But on the other hand, it really does look like some freakish plastic ice-cream or over-sized roll-on deodorant, and I’m not sure I want to play Fight Night Round 5 flailing my arms and dripping with sweat…

So the Sony device has buttons while the Natal is button free – and I can definitely see positives and negatives for both systems – but as to which will be the best, and which if any, I’ll really want to use, I genuinely have no idea until I can give them both a shot. And that assumes that the price is right… What do you reckon?