I'm just starting to work with Quaternions, but I'm having some annoying difficulties getting a simple FPS camera to work properly using Quaternions.
Basically, whenever I try to move the mouse, a triangle I draw on the screen goes crazy and moves off and on the screen faster than I can see. Also, the movement keys are not at all working as expected..
Here's my Camera .h file:
/*
* CameraSceneNode.h
*
* Created on: 2011-05-08
* Author: jarrett
*/
#ifndef CAMERASCENENODE_H_
#define CAMERASCENENODE_H_
#include "ICameraSceneNode.h"
#include "Quaternion.h"
//#include "IInputListener.h"
namespace icee {
namespace engine {
class CameraSceneNode: public ICameraSceneNode {
public:
CameraSceneNode();
CameraSceneNode(vmath::Vector3f position, vmath::Vector3f lookAt, bool active = true);
virtual ~CameraSceneNode();
// inherited from ICameraSceneNode
virtual void render(); // don't really need this
// inherited from ICameraSceneNode
virtual const vmath::Vector3f& getLookAt();
virtual void setLookAt(const vmath::Vector3f& newLookAt);
virtual void move(MOVE_DIRECTION dir, bool enabled);
// up/down
virtual void rotateX(float32 degrees);
// left/right
virtual void rotateY(float32 degrees);
virtual void tick(float32 time);
protected:
vmath::Vector3f lookAt_;
Quaternion rotation_;
sint32 prevX_, prevY_;
char8 movement_[NUM_MOVE_DIRECTIONS];
float32 moveSpeed_, rotSpeed_;
void clearMovementBuffer();
float32 xRot_, yRot_;
void initialize();
};
}
}
#endif /* CAMERASCENENODE_H_ */
And here's my Camera .cpp file:
/*
* CameraSceneNode.cpp
*
* Created on: 2011-05-08
* Author: jarrett
*/
#include "CameraSceneNode.h"
#include "../common/math/Math.h"
#include <GL/gl.h>
#include <iostream>
namespace icee {
namespace engine {
CameraSceneNode::CameraSceneNode() {
setLookAt(vmath::Vector3f(1, 1, 1));
setPosition(vmath::Vector3f(0, 0, 0));
active_ = true;
initialize();
}
CameraSceneNode::CameraSceneNode(vmath::Vector3f position, vmath::Vector3f lookAt, bool active) {
setPosition(position);
setLookAt(lookAt);
active_ = active;
initialize();
}
CameraSceneNode::~CameraSceneNode() {
}
void CameraSceneNode::initialize() {
clearMovementBuffer();
xRot_ = 0;
yRot_ = 0;
moveSpeed_ = 0.25f;
rotSpeed_ = 0.01f;
}
void CameraSceneNode::render() {
if (isActive()) {
float32 matrix[16];
rotation_.fillMatrix(matrix);
glMultMatrixf(&matrix[0]);
glTranslatef(-pos_.x, -pos_.y, -pos_.z);
}
}
const vmath::Vector3f& CameraSceneNode::getLookAt() {
return lookAt_;
}
void CameraSceneNode::setLookAt(const vmath::Vector3f& newLookAt) {
lookAt_ = newLookAt;
}
/**
*
*/
void CameraSceneNode::clearMovementBuffer() {
for (uint32 i = 0; i < NUM_MOVE_DIRECTIONS; i++)
movement_ = 0;
}
/**
*
*/
void CameraSceneNode::move(MOVE_DIRECTION dir, bool enabled) {
movement_[dir] = (enabled ? 1 : 0);
}
// up/down
/**
*
*/
void CameraSceneNode::rotateX(float32 degrees) {
xRot_ = degrees;
}
// left/right
/**
*
*/
void CameraSceneNode::rotateY(float32 degrees) {
yRot_ = degrees;
}
/**
*
*/
void CameraSceneNode::tick(float32 time) {
// movement direction
if (movement_[MOVE_DIR_FORWARD] == 1)
pos_ += rotation_ * vmath::Vector3f(0, 0, -moveSpeed_ * time);
if (movement_[MOVE_DIR_BACKWARD] == 1)
pos_ += rotation_ * vmath::Vector3f(0, 0, moveSpeed_ * time);
if (movement_[MOVE_DIR_LEFT] == 1)
pos_ += rotation_ * vmath::Vector3f(-moveSpeed_ * time, 0, 0);
if (movement_[MOVE_DIR_RIGHT] == 1)
pos_ += rotation_ * vmath::Vector3f(moveSpeed_ * time, 0, 0);
// rotation
if (xRot_ != 0) {
Quaternion quatRotation = Quaternion(vmath::Vector3f(1, 0, 0),
(xRot_ * time * rotSpeed_) * math::DEGTORAD);
rotation_ = rotation_ * quatRotation;
xRot_ = 0;
}
if (yRot_ != 0) {
Quaternion quatRotation = Quaternion(vmath::Vector3f(0, 1, 0),
(yRot_ * time * rotSpeed_) * math::DEGTORAD);
rotation_ = quatRotation * rotation_;
yRot_ = 0;
}
}
}
}
For good measure, here is also my Quaternion implementation:
/*
* Quaternion.h
*
* Created on: 2011-05-12
* Author: jarrett
*/
#ifndef QUATERNION_H_
#define QUATERNION_H_
#include "../vmath/Vector3f.h"
#include "../common/math/Math.h"
#include <math.h>
namespace icee {
namespace engine {
using namespace compatibility;
class Quaternion {
private:
float32 real_;
vmath::Vector3f imaginary_;
public:
/**
* Constructor.
*/
Quaternion() {
real_ = 1;
imaginary_ = vmath::Vector3f(0, 0, 0);
}
/**
* Constructor.
*/
Quaternion(float32 real, float32 x, float32 y, float32 z) {
real_ = real;
imaginary_ = vmath::Vector3f(x, y, z);
}
/**
* Constructor.
*/
Quaternion(float32 real, const vmath::Vector3f& imaginary) {
real_ = real;
imaginary_ = imaginary;
}
/**
* Constructor.
* Create this Quaternion from an axis angle. This is useful if you want to use the Quaternion
* to rotate through an angle about the axis (unit) vector. The equation for this is:
* q = cos(theta/2) + vector*sin(theta/2)
*/
Quaternion(const vmath::Vector3f& vec, float32 degrees) {
buildFromAxisAngle(vec.x, vec.y, vec.z, degrees);
/*
// convert from degrees to radians
float32 angle = ((degrees / 180.0f) * math::PI);
float32 sinAngle;
angle *= 0.5f;
vmath::Vector3f vector(vec);
vector.normalize();
sinAngle = sin(angle);
imaginary_.x = (vector.x * sinAngle);
imaginary_.y = (vector.y * sinAngle);
imaginary_.z = (vector.z * sinAngle);
real_ = cos(angle);
*/
}
/**
* Build this Quaternion from an axis angle. This is useful if you want to use the Quaternion
* to rotate through an angle about the axis (unit) vector. The equation for this is:
* q = cos(theta/2) + vector*sin(theta/2)
*/
void buildFromAxisAngle(float32 x, float32 y, float32 z, float32 degrees) {
// convert from degrees to radians
float32 angle = ((degrees / 180.0f) * math::PI);
float32 sinAngle;
angle *= 0.5f;
//vmath::Vector3f vector(vec);
//vector.normalize();
sinAngle = sin(angle);
imaginary_.x = (x * sinAngle);
imaginary_.y = (y * sinAngle);
imaginary_.z = (z * sinAngle);
real_ = cos(angle);
}
/*
// Convert from Euler Angles
Quaternion(float32 pitch, float32 yaw, float32 roll) {
// Basically we create 3 Quaternions, one for pitch, one for yaw, one for roll
// and multiply those together.
// the calculation below does the same, just shorter
float p = pitch * PIOVER180 / 2.0;
float y = yaw * PIOVER180 / 2.0;
float r = roll * PIOVER180 / 2.0;
float sinp = sin(p);
float siny = sin(y);
float sinr = sin(r);
float cosp = cos(p);
float cosy = cos(y);
float cosr = cos(r);
this->x = sinr * cosp * cosy - cosr * sinp * siny;
this->y = cosr * sinp * cosy + sinr * cosp * siny;
this->z = cosr * cosp * siny - sinr * sinp * cosy;
this->w = cosr * cosp * cosy + sinr * sinp * siny;
normalise();
}
*/
/**
* Copy constructor.
*/
Quaternion(const Quaternion &quat) {
real_ = quat.real_;
imaginary_ = quat.imaginary_;
}
/**
* Destructor.
*/
virtual ~Quaternion() {
}
const Quaternion& operator =(const Quaternion &quat) {
real_ = quat.real_;
imaginary_ = quat.imaginary_;
return *this;
}
const Quaternion operator +(const Quaternion &quat) const {
return Quaternion(real_ + quat.real_, imaginary_ + quat.imaginary_);
}
const Quaternion operator -(const Quaternion &quat) const {
return Quaternion(real_ - quat.real_, imaginary_ - quat.imaginary_);
}
const Quaternion operator *(const Quaternion &quat) const {
return Quaternion(
real_ * quat.real_ - imaginary_ * quat.imaginary_,
imaginary_.y * quat.imaginary_.z - imaginary_.z * quat.imaginary_.y + real_
* quat.imaginary_.x + imaginary_.x * quat.real_,
imaginary_.z * quat.imaginary_.x - imaginary_.x * quat.imaginary_.z + real_
* quat.imaginary_.y + imaginary_.y * quat.real_,
imaginary_.x * quat.imaginary_.y - imaginary_.y * quat.imaginary_.x + real_
* quat.imaginary_.z + imaginary_.z * quat.real_);
}
// Multiplying a quaternion q with a vector v applies the q-rotation to v
const vmath::Vector3f operator*(const vmath::Vector3f &vec) {
vmath::Vector3f vector(vec);
vector.normalize();
Quaternion vectorQuat, resultQuat;
vectorQuat.imaginary_.x = vector.x;
vectorQuat.imaginary_.y = vector.y;
vectorQuat.imaginary_.z = vector.z;
vectorQuat.real_ = 0.0f;
resultQuat = vectorQuat * getConjugate();
resultQuat = *this * resultQuat;
return (vmath::Vector3f(resultQuat.imaginary_.x, resultQuat.imaginary_.y,
resultQuat.imaginary_.z));
}
const Quaternion operator /(const Quaternion &quat) const {
Quaternion retQuat(quat);
retQuat.invert();
return *this * retQuat;
}
const Quaternion& operator /=(float32 scale) {
real_ /= scale;
imaginary_ /= scale;
return *this;
}
void invert() {
conjugate();
*this /= lengthSquared();
}
void conjugate() {
imaginary_ *= (-1.f);
}
Quaternion getConjugate() {
return Quaternion(-imaginary_.x, -imaginary_.y, -imaginary_.z, real_);
}
float32 length() const {
return (float32) (sqrt(real_ * real_ + imaginary_ * imaginary_));
}
/**
* Get the squared length of this quaternion.
*/
float32 lengthSquared() {
return (float32) (real_ * real_ + imaginary_ * imaginary_);
}
void normalize() {
*this /= length();
}
/**
*
* Fills in Column-Major order.
*/
void fillMatrix(float32 matrix[]) {
// First row
matrix[0] = 1.0f - 2.0f * (imaginary_.y * imaginary_.y + imaginary_.z * imaginary_.z);
matrix[1] = 2.0f * (imaginary_.x * imaginary_.y + imaginary_.z * real_);
matrix[2] = 2.0f * (imaginary_.x * imaginary_.z - imaginary_.y * real_);
matrix[3] = 0.0f;
// Second row
matrix[4] = 2.0f * (imaginary_.x * imaginary_.y - imaginary_.z * real_);
matrix[5] = 1.0f - 2.0f * (imaginary_.x * imaginary_.x + imaginary_.z * imaginary_.z);
matrix[6] = 2.0f * (imaginary_.z * imaginary_.y + imaginary_.x * real_);
matrix[7] = 0.0f;
// Third row
matrix[8] = 2.0f * (imaginary_.x * imaginary_.z + imaginary_.y * real_);
matrix[9] = 2.0f * (imaginary_.y * imaginary_.z - imaginary_.x * real_);
matrix[10] = 1.0f - 2.0f * (imaginary_.x * imaginary_.x + imaginary_.y * imaginary_.y);
matrix[11] = 0.0f;
// Fourth row
matrix[12] = 0;
matrix[13] = 0;
matrix[14] = 0;
matrix[15] = 1.0f;
}
/*
// Convert to Matrix
Matrix4 getMatrix() const {
float x2 = x * x;
float y2 = y * y;
float z2 = z * z;
float xy = x * y;
float xz = x * z;
float yz = y * z;
float wx = w * x;
float wy = w * y;
float wz = w * z;
// This calculation would be a lot more complicated for non-unit length quaternions
// Note: The constructor of Matrix4 expects the Matrix in column-major format like expected by
// OpenGL
return Matrix4( 1.0f - 2.0f * (y2 + z2), 2.0f * (xy - wz), 2.0f * (xz + wy), 0.0f,
2.0f * (xy + wz), 1.0f - 2.0f * (x2 + z2), 2.0f * (yz - wx), 0.0f,
2.0f * (xz - wy), 2.0f * (yz + wx), 1.0f - 2.0f * (x2 + y2), 0.0f,
0.0f, 0.0f, 0.0f, 1.0f)
}
*/
/*
// Convert to Axis/Angles
void getAxisAngle(Vector3* axis, float* angle) {
float scale = sqrt(x * x + y * y + z * z);
axis->x = x / scale;
axis->y = y / scale;
axis->z = z / scale;
*angle = acos(w) * 2.0f;
}
*/
};
}
}
#endif /* QUATERNION_H_ */
It is important to mention that before CameraSceneNode::render() gets called, the following bit of code is called first:
glMatrixMode(GL_MODELVIEW);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
What happens in my game loop is that CameraSceneNode::tick(..) gets called, which updates the movement/rotation variables. Then after some other code, CameraSceneNode::render() is called.
I know that's a lot of code to look over, but I would really appreciate it if anyone could see any (hopefully!) glaring omissions or errors on my part. Also, if something isn't clear just ask!
Thanks all,
Jarrett