Crazy Quaternion

Hi guys,

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.

Hopefully this is the right place to ask about this.

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 (wasd) 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;

	rotation_ = Quaternion();//(1, 1, 1, 1);
	rotation_.normalize();
}

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_[i] = 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();
		quatRotation.buildFromAxisAngle(1, 0, 0, (xRot_ * time * rotSpeed_) * math::DEGTORAD);
		quatRotation.normalize();
		rotation_ = rotation_ * quatRotation;
		xRot_ = 0;
		rotation_.normalize();
	}
	if (yRot_ != 0) {
		Quaternion quatRotation = Quaternion();
		quatRotation.buildFromAxisAngle(0, 1, 0, (yRot_ * time * rotSpeed_) * math::DEGTORAD);
		quatRotation.normalize();
		rotation_ = quatRotation * rotation_;
		yRot_ = 0;
		rotation_.normalize();
	}

}

}

}


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 x, float32 y, float32 z, float32 real) {
		real_ = real;
		imaginary_ = vmath::Vector3f(x, y, z);
	}

	/**
	 * Constructor.
	 */
	Quaternion(const vmath::Vector3f& imaginary, float32 real) {
		real_ = real;
		imaginary_ = imaginary;
	}

	/**
	 * 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)
	 *
	 * @param x
	 * @param y
	 * @param z
	 * @param angle The angle, in radians
	 */
	void buildFromAxisAngle(float32 x, float32 y, float32 z, float32 angle) {
		// 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(imaginary_ + quat.imaginary_, real_ + quat.real_);
	}

	const Quaternion operator -(const Quaternion &quat) const {
		return Quaternion(imaginary_ - quat.imaginary_, real_ - quat.real_);
	}

	const Quaternion operator *(const Quaternion &quat) const {
		return Quaternion(
				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_,
				real_ * quat.real_ - imaginary_ * quat.imaginary_);
	}

	// 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 col
		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 col
		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 col
		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 col
		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

Here is a quick video of what is happening:

http://jarrettchisholm.com/downloads/QuaternionTest.mpeg

  • To see mouse input, watch from the beginning.
  • To see to keyboard input (go forward, backward, etc) skip to 0:51 seconds.