OpenGL Camera
UPDATE: For a modern opengl version using GLM please see this newer post
UPDATE: Some definitions were mission from code, quaternion and vector classes now included with code
When i’m writing a dynamics code I usually need to visually debug my simulations to make sure everything is initialized properly, looks correct, etc. I’ve found that using OpenGL along with GLUT provides a lightweight solution that I can implement quickly. Usually when I implement a basic rendering code I have a static camera, which in the grand scheme of things is not very useful. So after googling OpenGL quaternion cameras I came up with the following solution. To use this code a basic quaternion class is included. This will be covered in a later post.
Initialize the camera
The camera requires:
- a position
- a point to look at
- the “up” direction, y is usually up
- a scale for movement
OpenGLCamera oglcamera(real3(0,0,-1), real3(0,0,0),real3(0,1,0),1);
I will update this code with some comments soon. ## Base camera class
////////////////////////Quaternion and Vector Code////////////////////////
typedef double real;
struct real3 {
real3(real a = 0, real b = 0, real c = 0): x(a), y(b), z(c) {}
real x, y, z;
};
struct real4 {
real4(real d = 0, real a = 0, real b = 0, real c = 0): w(d), x(a), y(b), z(c) {}
real w, x, y, z;
};
static real3 operator +(const real3 rhs, const real3 lhs)
{
real3 temp;
temp.x = rhs.x + lhs.x;
temp.y = rhs.y + lhs.y;
temp.z = rhs.z + lhs.z;
return temp;
}
static real3 operator -(const real3 rhs, const real3 lhs)
{
real3 temp;
temp.x = rhs.x - lhs.x;
temp.y = rhs.y - lhs.y;
temp.z = rhs.z - lhs.z;
return temp;
}
static void operator +=(real3 &rhs, const real3 lhs)
{
rhs = rhs + lhs;
}
static void operator -=(real3 &rhs, const real3 lhs)
{
rhs = rhs - lhs;
}
static real3 operator *(const real3 rhs, const real3 lhs)
{
real3 temp;
temp.x = rhs.x * lhs.x;
temp.y = rhs.y * lhs.y;
temp.z = rhs.z * lhs.z;
return temp;
}
static real3 operator *(const real3 rhs, const real lhs)
{
real3 temp;
temp.x = rhs.x * lhs;
temp.y = rhs.y * lhs;
temp.z = rhs.z * lhs;
return temp;
}
static inline real3 cross(real3 a, real3 b)
{
return real3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x);
}
static real4 Q_from_AngAxis(real angle, real3 axis)
{
real4 quat;
real halfang;
real sinhalf;
halfang = (angle * 0.5);
sinhalf = sin(halfang);
quat.w = cos(halfang);
quat.x = axis.x * sinhalf;
quat.y = axis.y * sinhalf;
quat.z = axis.z * sinhalf;
return (quat);
}
static real4 normalize(const real4 &a)
{
real length = 1.0 / sqrt(a.w * a.w + a.x * a.x + a.y * a.y + a.z * a.z);
return real4(a.w * length, a.x * length, a.y * length, a.z * length);
}
static inline real4 inv(real4 a)
{
//return (1.0f / (dot(a, a))) * F4(a.x, -a.y, -a.z, -a.w);
real4 temp;
real t1 = a.w * a.w + a.x * a.x + a.y * a.y + a.z * a.z;
t1 = 1.0 / t1;
temp.w = t1 * a.w;
temp.x = -t1 * a.x;
temp.y = -t1 * a.y;
temp.z = -t1 * a.z;
return temp;
}
static inline real4 mult(const real4 &a, const real4 &b)
{
real4 temp;
temp.w = a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z;
temp.x = a.w * b.x + b.w * a.x + a.y * b.z - a.z * b.y;
temp.y = a.w * b.y + b.w * a.y + a.z * b.x - a.x * b.z;
temp.z = a.w * b.z + b.w * a.z + a.x * b.y - a.y * b.x;
return temp;
}
static inline real3 quatRotate(const real3 &v, const real4 &q)
{
real4 r = mult(mult(q, real4(0, v.x, v.y, v.z)), inv(q));
return real3(r.x, r.y, r.z);
}
static real4 operator %(const real4 rhs, const real4 lhs)
{
return mult(rhs, lhs);
}
////////////////////////END Quaternion and Vector Code////////////////////////
class OpenGLCamera
{
public:
OpenGLCamera(real3 pos, real3 lookat, real3 up, real viewscale) {
max_pitch_rate = 5;
max_heading_rate = 5;
camera_pos = pos;
look_at = lookat;
camera_up = up;
camera_heading = 0;
camera_pitch = 0;
dir = real3(0, 0, 1);
mouse_pos = real3(0, 0, 0);
camera_pos_delta = real3(0, 0, 0);
scale = viewscale;
}
void ChangePitch(GLfloat degrees) {
if (fabs(degrees) < fabs(max_pitch_rate)) {
camera_pitch += degrees;
} else {
if (degrees < 0) {
camera_pitch -= max_pitch_rate;
} else {
camera_pitch += max_pitch_rate;
}
}
if (camera_pitch > 360.0f) {
camera_pitch -= 360.0f;
} else if (camera_pitch < -360.0f) {
camera_pitch += 360.0f;
}
}
void ChangeHeading(GLfloat degrees) {
if (fabs(degrees) < fabs(max_heading_rate)) {
if (camera_pitch > 90 && camera_pitch < 270 || (camera_pitch < -90 && camera_pitch > -270)) {
camera_heading -= degrees;
} else {
camera_heading += degrees;
}
} else {
if (degrees < 0) {
if ((camera_pitch > 90 && camera_pitch < 270) || (camera_pitch < -90 && camera_pitch > -270)) {
camera_heading += max_heading_rate;
} else {
camera_heading -= max_heading_rate;
}
} else {
if (camera_pitch > 90 && camera_pitch < 270 || (camera_pitch < -90 && camera_pitch > -270)) {
camera_heading -= max_heading_rate;
} else {
camera_heading += max_heading_rate;
}
}
}
if (camera_heading > 360.0f) {
camera_heading -= 360.0f;
} else if (camera_heading < -360.0f) {
camera_heading += 360.0f;
}
}
void Move2D(int x, int y) {
real3 mouse_delta = mouse_pos - real3(x, y, 0);
ChangeHeading(.02 * mouse_delta.x);
ChangePitch(.02 * mouse_delta.y);
mouse_pos = real3(x, y, 0);
}
void SetPos(int button, int state, int x, int y) {
mouse_pos = real3(x, y, 0);
}
void Update() {
real4 pitch_quat, heading_quat;
real3 angle;
angle = cross(dir, camera_up);
pitch_quat = Q_from_AngAxis(camera_pitch, angle);
heading_quat = Q_from_AngAxis(camera_heading, camera_up);
real4 temp = (pitch_quat % heading_quat);
temp = normalize(temp);
dir = quatRotate(dir, temp);
camera_pos += camera_pos_delta;
look_at = camera_pos + dir * 1;
camera_heading *= .5;
camera_pitch *= .5;
camera_pos_delta = camera_pos_delta * .5;
gluLookAt(camera_pos.x, camera_pos.y, camera_pos.z, look_at.x, look_at.y, look_at.z, camera_up.x, camera_up.y, camera_up.z);
}
void Forward() {
camera_pos_delta += dir * scale;
}
void Back() {
camera_pos_delta -= dir * scale;
}
void Right() {
camera_pos_delta += cross(dir, camera_up) * scale;
}
void Left() {
camera_pos_delta -= cross(dir, camera_up) * scale;
}
void Up() {
camera_pos_delta -= camera_up * scale;
}
void Down() {
camera_pos_delta += camera_up * scale;
}
real max_pitch_rate, max_heading_rate;
real3 camera_pos, look_at, camera_up;
real camera_heading, camera_pitch, scale;
real3 dir, mouse_pos, camera_pos_delta;
};
Using with GLUT
In order to use this code with the GLUT library a few more functions are required, it is assumed that oglcamera a OpenGLCamera object.
//OpenGLCamera oglcamera
void CallBackKeyboardFunc(unsigned char key, int x, int y)
{
switch (key) {
case 'w':
oglcamera.Forward();
break;
case 's':
oglcamera.Back();
break;
case 'd':
oglcamera.Right();
break;
case 'a':
oglcamera.Left();
break;
case 'q':
oglcamera.Up();
break;
case 'e':
oglcamera.Down();
break;
}
}
void CallBackMouseFunc(int button, int state, int x, int y)
{
oglcamera.SetPos(button, state, x, y);
}
void CallBackMotionFunc(int x, int y)
{
oglcamera.Move2D(x, y);
}
GKR7HYRJ3T2Q