This is an update to my previous OpenGL camera class . It uses GLM for all math operations and is better suited for use with modern opengl code based on shaders and such.
Code is also availible here:
https://github.com/hmazhar/moderngl_camera
Initialize the camera
The camera requires:
a mode, Either FREE or ORTHO
a position
a point to look at
clipping information
the field of View
Camera camera ;
camera . SetMode ( FREE ); //Two Modes FREE and ORTHO
camera . SetPosition ( glm :: vec3 ( 0 , 0 , - 10 ));
camera . SetLookAt ( glm :: vec3 ( 0 , 0 , 0 ));
camera . SetClipping ( .01 , 500 );
camera . SetFOV ( 45 );
Base camera class
/*
camera.h
OpenGL Camera Code
Capable of 2 modes, orthogonal, and free
Quaternion camera code adapted from: http://hamelot.co.uk/visualization/opengl-camera/
Written by Hammad Mazhar
*/
#ifndef CAMERA_H
#define CAMERA_H
#include <GL/freeglut.h>
#include <glm/glm.hpp>
#include <glm/gtx/transform.hpp>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtx/quaternion.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
enum CameraType {
ORTHO , FREE
};
enum CameraDirection {
UP , DOWN , LEFT , RIGHT , FORWARD , BACK
};
class Camera {
public :
Camera ();
~ Camera ();
void Reset ();
//This function updates the camera
//Depending on the current camera mode, the projection and viewport matricies are computed
//Then the position and location of the camera is updated
void Update ();
//Given a specific moving direction, the camera will be moved in the appropriate direction
//For a spherical camera this will be around the look_at point
//For a free camera a delta will be computed for the direction of movement.
void Move ( CameraDirection dir );
//Change the pitch (up, down) for the free camera
void ChangePitch ( float degrees );
//Change heading (left, right) for the free camera
void ChangeHeading ( float degrees );
//Change the heading and pitch of the camera based on the 2d movement of the mouse
void Move2D ( int x , int y );
//Setting Functions
//Changes the camera mode, only three valid modes, Ortho, Free, and Spherical
void SetMode ( CameraType cam_mode );
//Set the position of the camera
void SetPosition ( glm :: vec3 pos );
//Set's the look at point for the camera
void SetLookAt ( glm :: vec3 pos );
//Changes the Field of View (FOV) for the camera
void SetFOV ( double fov );
//Change the viewport location and size
void SetViewport ( int loc_x , int loc_y , int width , int height );
//Change the clipping distance for the camera
void SetClipping ( double near_clip_distance , double far_clip_distance );
void SetDistance ( double cam_dist );
void SetPos ( int button , int state , int x , int y );
//Getting Functions
CameraType GetMode ();
void GetViewport ( int & loc_x , int & loc_y , int & width , int & height );
void GetMatricies ( glm :: mat4 & P , glm :: mat4 & V , glm :: mat4 & M );
CameraType camera_mode ;
int viewport_x ;
int viewport_y ;
int window_width ;
int window_height ;
double aspect ;
double field_of_view ;
double near_clip ;
double far_clip ;
float camera_scale ;
float camera_heading ;
float camera_pitch ;
float max_pitch_rate ;
float max_heading_rate ;
bool move_camera ;
glm :: vec3 camera_position ;
glm :: vec3 camera_position_delta ;
glm :: vec3 camera_look_at ;
glm :: vec3 camera_direction ;
glm :: vec3 camera_up ;
glm :: quat rotation_quaternion ;
glm :: vec3 mouse_position ;
glm :: mat4 projection ;
glm :: mat4 view ;
glm :: mat4 model ;
glm :: mat4 MVP ;
};
#endif
//camera.cpp
#include "camera.h"
using namespace std ;
Camera :: Camera () {
camera_mode = FREE ;
camera_up = glm :: vec3 ( 0 , 1 , 0 );
field_of_view = 45 ;
rotation_quaternion = glm :: quat ( 1 , 0 , 0 , 0 );
camera_position_delta = glm :: vec3 ( 0 , 0 , 0 );
camera_scale = .5 f ;
max_pitch_rate = 5 ;
max_heading_rate = 5 ;
move_camera = false ;
}
Camera ::~ Camera () {
}
void Camera :: Reset () {
camera_up = glm :: vec3 ( 0 , 1 , 0 );
}
void Camera :: Update () {
camera_direction = glm :: normalize ( camera_look_at - camera_position );
//need to set the matrix state. this is only important because lighting doesn't work if this isn't done
glMatrixMode ( GL_PROJECTION );
glLoadIdentity ();
glViewport ( viewport_x , viewport_y , window_width , window_height );
if ( camera_mode == ORTHO ) {
//our projection matrix will be an orthogonal one in this case
//if the values are not floating point, this command does not work properly
//need to multiply by aspect!!! (otherise will not scale properly)
projection = glm :: ortho ( - 1.5 f * float ( aspect ), 1.5 f * float ( aspect ), - 1.5 f , 1.5 f , - 10.0 f , 10. f );
} else if ( camera_mode == FREE ) {
projection = glm :: perspective ( field_of_view , aspect , near_clip , far_clip );
//detmine axis for pitch rotation
glm :: vec3 axis = glm :: cross ( camera_direction , camera_up );
//compute quaternion for pitch based on the camera pitch angle
glm :: quat pitch_quat = glm :: angleAxis ( camera_pitch , axis );
//determine heading quaternion from the camera up vector and the heading angle
glm :: quat heading_quat = glm :: angleAxis ( camera_heading , camera_up );
//add the two quaternions
glm :: quat temp = glm :: cross ( pitch_quat , heading_quat );
temp = glm :: normalize ( temp );
//update the direction from the quaternion
camera_direction = glm :: rotate ( temp , camera_direction );
//add the camera delta
camera_position += camera_position_delta ;
//set the look at to be infront of the camera
camera_look_at = camera_position + camera_direction * 1.0 f ;
//damping for smooth camera
camera_heading *= .5 ;
camera_pitch *= .5 ;
camera_position_delta = camera_position_delta * .8 f ;
}
//compute the MVP
view = glm :: lookAt ( camera_position , camera_look_at , camera_up );
model = glm :: mat4 ( 1.0 f );
MVP = projection * view * model ;
glLoadMatrixf ( glm :: value_ptr ( MVP ));
}
//Setting Functions
void Camera :: SetMode ( CameraType cam_mode ) {
camera_mode = cam_mode ;
camera_up = glm :: vec3 ( 0 , 1 , 0 );
rotation_quaternion = glm :: quat ( 1 , 0 , 0 , 0 );
}
void Camera :: SetPosition ( glm :: vec3 pos ) {
camera_position = pos ;
}
void Camera :: SetLookAt ( glm :: vec3 pos ) {
camera_look_at = pos ;
}
void Camera :: SetFOV ( double fov ) {
field_of_view = fov ;
}
void Camera :: SetViewport ( int loc_x , int loc_y , int width , int height ) {
viewport_x = loc_x ;
viewport_y = loc_y ;
window_width = width ;
window_height = height ;
//need to use doubles division here, it will not work otherwise and it is possible to get a zero aspect ratio with integer rounding
aspect = double ( width ) / double ( height );
;
}
void Camera :: SetClipping ( double near_clip_distance , double far_clip_distance ) {
near_clip = near_clip_distance ;
far_clip = far_clip_distance ;
}
void Camera :: Move ( CameraDirection dir ) {
if ( camera_mode == FREE ) {
switch ( dir ) {
case UP :
camera_position_delta += camera_up * camera_scale ;
break ;
case DOWN :
camera_position_delta -= camera_up * camera_scale ;
break ;
case LEFT :
camera_position_delta -= glm :: cross ( camera_direction , camera_up ) * camera_scale ;
break ;
case RIGHT :
camera_position_delta += glm :: cross ( camera_direction , camera_up ) * camera_scale ;
break ;
case FORWARD :
camera_position_delta += camera_direction * camera_scale ;
break ;
case BACK :
camera_position_delta -= camera_direction * camera_scale ;
break ;
}
}
}
void Camera :: ChangePitch ( float degrees ) {
//Check bounds with the max pitch rate so that we aren't moving too fast
if ( degrees < - max_pitch_rate ) {
degrees = - max_pitch_rate ;
} else if ( degrees > max_pitch_rate ) {
degrees = max_pitch_rate ;
}
camera_pitch += degrees ;
//Check bounds for the camera pitch
if ( camera_pitch > 360.0 f ) {
camera_pitch -= 360.0 f ;
} else if ( camera_pitch < - 360.0 f ) {
camera_pitch += 360.0 f ;
}
}
void Camera :: ChangeHeading ( float degrees ) {
//Check bounds with the max heading rate so that we aren't moving too fast
if ( degrees < - max_heading_rate ) {
degrees = - max_heading_rate ;
} else if ( degrees > max_heading_rate ) {
degrees = max_heading_rate ;
}
//This controls how the heading is changed if the camera is pointed straight up or down
//The heading delta direction changes
if ( camera_pitch > 90 && camera_pitch < 270 || ( camera_pitch < - 90 && camera_pitch > - 270 )) {
camera_heading -= degrees ;
} else {
camera_heading += degrees ;
}
//Check bounds for the camera heading
if ( camera_heading > 360.0 f ) {
camera_heading -= 360.0 f ;
} else if ( camera_heading < - 360.0 f ) {
camera_heading += 360.0 f ;
}
}
void Camera :: Move2D ( int x , int y ) {
//compute the mouse delta from the previous mouse position
glm :: vec3 mouse_delta = mouse_position - glm :: vec3 ( x , y , 0 );
//if the camera is moving, meaning that the mouse was clicked and dragged, change the pitch and heading
if ( move_camera ) {
ChangeHeading ( .08 f * mouse_delta . x );
ChangePitch ( .08 f * mouse_delta . y );
}
mouse_position = glm :: vec3 ( x , y , 0 );
}
void Camera :: SetPos ( int button , int state , int x , int y ) {
if ( button == 3 && state == GLUT_DOWN ) {
camera_position_delta += camera_up * .05 f ;
} else if ( button == 4 && state == GLUT_DOWN ) {
camera_position_delta -= camera_up * .05 f ;
} else if ( button == GLUT_LEFT_BUTTON && state == GLUT_DOWN ) {
move_camera = true ;
} else if ( button == GLUT_LEFT_BUTTON && state == GLUT_UP ) {
move_camera = false ;
}
mouse_position = glm :: vec3 ( x , y , 0 );
}
CameraType Camera :: GetMode () {
return camera_mode ;
}
void Camera :: GetViewport ( int & loc_x , int & loc_y , int & width , int & height ) {
loc_x = viewport_x ;
loc_y = viewport_y ;
width = window_width ;
height = window_height ;
}
void Camera :: GetMatricies ( glm :: mat4 & P , glm :: mat4 & V , glm :: mat4 & M ) {
P = projection ;
V = view ;
M = model ;
}
Using with GLUT
In order to use this code with the GLUT library a few more functions are required, it is assumed that camera is a Camera object.
//Camera camera
void CallBackKeyboardFunc ( unsigned char key , int x , int y )
{
switch ( key ) {
case 'w' :
camera . Move ( FORWARD );
break ;
case 'a' :
camera . Move ( LEFT );
break ;
case 's' :
camera . Move ( BACK );
break ;
case 'd' :
camera . Move ( RIGHT );
break ;
case 'q' :
camera . Move ( DOWN );
break ;
case 'e' :
camera . Move ( UP );
break ;
}
}
void CallBackMouseFunc ( int button , int state , int x , int y )
{
camera . SetPos ( button , state , x , y );
}
void CallBackMotionFunc ( int x , int y )
{
camera . Move2D ( x , y );
}