Archived

This topic is now archived and is closed to further replies.

singulab

Desperate Quaternions

Recommended Posts

I have recently written a quaternion implementation due to the fact that the tutorials here do not allow rotation about any vector. I don't know if it indeed works because there is a warning that says I should not multiply quats with different coordinate systems but that, I think, is precisely what I did but it seemed to run well! I have the code below; could anyone please evaluate? By the way, I would like to ask if there is any good camera classes available? I would very much like to know about it before I start my first project.

//== CODE ======================================================

//======== based on Lesson 01 ==================================



//I call, in DrawGLScene():


//---


// c is the CCamera class below


	if (keys[VK_NUMPAD8])
		c.Rotate(1, 0, 0, -0.02);
	if (keys[VK_NUMPAD2])
		c.Rotate(1, 0, 0, 0.02);
	if (keys[VK_NUMPAD1])
		c.Rotate(0, 1, 0, 0.02);
	if (keys[VK_NUMPAD3])
		c.Rotate(0, 1, 0, -0.02);
	if (keys[VK_NUMPAD4])
		c.Rotate(0, 0, 1, 0.02);
	if (keys[VK_NUMPAD6])
		c.Rotate(0, 0, 1, -0.02);
	if (keys[VK_ADD])
		c.Translate(0, 0, -0.1);
	if (keys[VK_SUBTRACT])
		c.Translate(0, 0, 0.1);

// ...


c.Apply()

//---


//And the related definitions:


//---


// CCamera.h

// Header file for CCamera.cpp


#ifndef CCAMERA_H
#define CCAMERA_H

#include "CVector3.h"
#include "CQuat.h"

class CCamera {
private:
	CQuaternion m_q;										// Rotation

	CVector3 m_pos;											// Position

	CVector3 m_ia, m_ja, m_ka;								// Unit Axes


	static const CVector3 I_AXIS, J_AXIS, K_AXIS;
	static const double PI;

	inline double RadToDeg(double) const;

public:
	CCamera();												// Constructor


	// Transformations

	void Rotate(double, double, double, double);			// Rotate about a vector

	void Translate(double, double, double, bool = false);	// Translate orthogonally


	// Effect changes

	void Apply() const;										// Apply changes

	void LookAt(double, double, double) const;				// Look at a point

};

inline double CCamera::RadToDeg(double angle) const
{
	return angle * 180 / PI;
}

#endif // CCAMERA_H


-----------------------------------------

// CCamera.cpp

// Camera control in OpenGL


#include <windows.h>
#include <gl\gl.h>
#include <gl\glu.h>

#include <math.h>
#include "CVector3.h"
#include "CQuat.h"
#include "CCamera.h"

const CVector3 CCamera::I_AXIS(1, 0, 0);
const CVector3 CCamera::J_AXIS(0, 1, 0);
const CVector3 CCamera::K_AXIS(0, 0, 1);

const double CCamera::PI = 3.141592653589793238462643383279502884197;

CCamera::CCamera()
{
	m_q = CQuaternion(1, 0, 0, 0);
	m_pos = CVector3(0, 0, 0);
	m_ia = I_AXIS;
	m_ja = J_AXIS;
	m_ka = K_AXIS;
}

void CCamera::Rotate(double i, double j, double k, double angle)
{
	m_q = m_q * CQuaternion(CVector3(i, j, k), angle);
	m_ia = m_q.Rotate(I_AXIS);
	m_ja = m_q.Rotate(J_AXIS);
	m_ka = m_q.Rotate(K_AXIS);
}

void CCamera::Translate(double i, double j, double k, bool global)
{
	if (global)
		m_pos = m_pos + CVector3(i, j, k);
	else
		m_pos = m_pos + m_ia * i + m_ja * j + m_ka * k;
}

void CCamera::Apply() const
{
	double dSinHalf = sqrt(1 - (m_q.s) * (m_q.s));
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glRotated(RadToDeg(-acos(m_q.s)) * 2, m_q.i / dSinHalf, m_q.j / dSinHalf, m_q.k / dSinHalf);
	glTranslated(-m_pos.i, -m_pos.j, -m_pos.k);
}

void CCamera::LookAt(double i, double j, double k) const
{
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(m_pos.i, m_pos.j, m_pos.k, i, j, k, m_ja.i, m_ja.j, m_ja.k);
}

---------------------------------------

/********************************

CVector3 and CQuaternion are just
basic 3d vectors and quaternions

 ********************************/

// CVector3.h

// Header file for CVector3.cpp


#ifndef CVECTOR3_H
#define CVECTOR3_H

class CVector3
{
public:
	double i, j, k;										// Components


	CVector3();											// Zero Vector

	CVector3(double, double, double);					// Vector

	const CVector3 operator + (const CVector3&) const;	// Addition

	const CVector3 operator - (const CVector3&) const;	// Subtraction

	const double operator * (const CVector3&) const;	// Dot product

	const CVector3 operator % (const CVector3&) const;	// Cross product

	const CVector3 operator * (const double&) const;	// Scalar Product

	const CVector3 operator / (const double&) const;	// Scalar Division

	const double Norm() const;							// Norm

	const CVector3 Unit() const;						// Unit Vector

};

#endif // CVECTOR3_H


-----------------------------------------

// CVector3.cpp

// Three-dimensional vector


#include <math.h>
#include "CVector3.h"

CVector3::CVector3()
{
	i = j = k = 0.0;
}

CVector3::CVector3(double i, double j, double k)
{
	this->i = i;
	this->j = j;
	this->k = k;
}

const CVector3 CVector3::operator + (const CVector3 &v) const
{
	return CVector3(i + v.i, j + v.j, k + v.k);
}

const CVector3 CVector3::operator - (const CVector3 &v) const
{
	return CVector3(i - v.i, j - v.j, k - v.k);
}

const double CVector3::operator * (const CVector3 &v) const
{
	return i * v.i + j * v.j + k * v.k;
}

const CVector3 CVector3::operator % (const CVector3 &v) const
{
	return CVector3(j * v.k - k * v.j, k * v.i - i * v.k, i * v.j - j * v.i);
}

const CVector3 CVector3::operator * (const double &d) const
{
	return CVector3(i * d, j * d, k * d);
}

const CVector3 CVector3::operator / (const double &d) const
{
	return CVector3(i / d, j / d, k / d);
}

const double CVector3::Norm() const
{
	return sqrt(i * i + j * j + k * k);
}

const CVector3 CVector3::Unit() const
{
	return *this / this->Norm();
}

-----------------------------------------

// CQuat.h

// Header file for CQuat.cpp


#ifndef CQUAT_H
#define CQUAT_H

#include "CVector3.h"

class CQuaternion
{
public:
	double s, i, j, k;											// Components


	CQuaternion();												// Multiplicative Identity

	CQuaternion(double, double, double, double);				// Direct initialisation

	CQuaternion(double, CVector3);								// Initialise with scalar and vector

	CQuaternion(CVector3, double);								// Rotation about unit vector

	const CQuaternion operator + (const CQuaternion&) const;	// Addition

	const CQuaternion operator - (const CQuaternion&) const;	// Subtraction

	const CQuaternion operator * (const CQuaternion&) const;	// Multiplication

	const CQuaternion operator / (const CQuaternion&) const;	// Division

	const CQuaternion operator * (const double&) const;			// Scalar Multiplication

	const CQuaternion operator / (const double&) const;			// Scalar Division

	const double Norm() const;									// Norm

	const CQuaternion Conjugate() const;						// Conjugate

	const CQuaternion Inverse() const;							// Inverse

	const CVector3 Rotate(const CVector3) const;				// Rotate a point

};

#endif // CQUAT_H


-----------------------------------------

// CQuat.cpp

// A quaternion


#include <math.h>
#include "CVector3.h"
#include "CQuat.h"

CQuaternion::CQuaternion()
{
	s = 1;
	i = j = k = 0;
}

CQuaternion::CQuaternion(double s, double i, double j, double k)
{
	this->s = s;
	this->i = i;
	this->j = j;
	this->k = k;
}

CQuaternion::CQuaternion(double s, CVector3 v)
{
	this->s = s;
	i = v.i;
	j = v.j;
	k = v.k;
}

CQuaternion::CQuaternion(CVector3 v, double angle)
{
	CVector3 uv = v.Unit();
	double dSinHalf = (double) sin(angle / 2);
	s = (double) cos(angle / 2);
	i = uv.i * dSinHalf;
	j = uv.j * dSinHalf;
	k = uv.k * dSinHalf;
}

const CQuaternion CQuaternion::operator + (const CQuaternion &q) const
{
	return CQuaternion(s + q.s, i + q.i, j + q.j, k + q.k);
}

const CQuaternion CQuaternion::operator - (const CQuaternion &q) const
{
	return CQuaternion(s - q.s, i - q.i, j - q.j, k - q.k);
}

const CQuaternion CQuaternion::operator * (const CQuaternion &q) const
{
	return CQuaternion(
		s * q.s - i * q.i - j * q.j - k * q.k,
		s * q.i + i * q.s + j * q.k - k * q.j,
		s * q.j - i * q.k + j * q.s + k * q.i,
		s * q.k + i * q.j - j * q.i + k * q.s);
}

const CQuaternion CQuaternion::operator / (const CQuaternion &q) const
{
	return (*this) * q.Inverse();
}

const CQuaternion CQuaternion::operator * (const double &d) const
{
	return CQuaternion(s * d, i * d, j * d, k * d);
}

const CQuaternion CQuaternion::operator / (const double &d) const
{
	return CQuaternion(s / d, i / d, j / d, k / d);
}

const double CQuaternion::Norm() const
{
	return (double) sqrt(s * s + i * i + j * j + k * k);
}

const CQuaternion CQuaternion::Inverse() const
{
	return this->Conjugate() / (this->Norm() * this->Norm());
}

const CQuaternion CQuaternion::Conjugate() const
{
	return CQuaternion(s, -i, -j, -k);
}

const CVector3 CQuaternion::Rotate(const CVector3 pt) const
{
	CQuaternion q = (*this) * CQuaternion(0, pt) * this->Conjugate();
	return CVector3(q.i, q.j, q.k);
}

===========================================================

Sorry if I am wordy but the code is long Thanks in advance [edited by - singulab on January 7, 2004 5:11:50 AM] [edited by - singulab on January 7, 2004 9:20:42 AM]

Share this post


Link to post
Share on other sites
Hey I don''t have time to look at this now, but when posing code its nice if you put it between some [*source] [*/source] (dont use the *''s.

Share this post


Link to post
Share on other sites
Following an afterthought this is proposed:



// global: true if rotating in global coordinate system


void CCamera::Rotate(double i, double j, double k, double angle, bool global)
{
if (global)
m_q = m_q * CQuaternion(CVector3(i, j, k), angle);
else
m_q = m_q * CQuaternion(m_ia * i + m_ja * j + m_ka * k, angle);
m_ia = m_q.Rotate(I_AXIS);
m_ja = m_q.Rotate(J_AXIS);
m_ka = m_q.Rotate(K_AXIS);
}


Please kindly evaluate it and tell me which one is correct.

Thanks

Share this post


Link to post
Share on other sites
One thing you are missing in your multiply code is the normalisation of the quaternion. You need to make sure that you keep the quaternion normalised at all times otherwise the quaternion is not representing a rotation(you are allowing an extra Degree of freedom which is eliminated by keeping the quaternion as a unit quaternion).

James

Share this post


Link to post
Share on other sites
Actually my quat is clever enough to convert all vectors to unit ones . cf.


CQuaternion::CQuaternion(CVector3 v, double angle){
CVector3 uv = v.Unit();
double dSinHalf = (double) sin(angle / 2);
s = (double) cos(angle / 2);
i = uv.i * dSinHalf;
j = uv.j * dSinHalf;
k = uv.k * dSinHalf;
}

Share this post


Link to post
Share on other sites
quote:
Original post by singulab
Actually my quat is clever enough to convert all vectors to unit ones . cf.


CQuaternion::CQuaternion(CVector3 v, double angle){
CVector3 uv = v.Unit();
double dSinHalf = (double) sin(angle / 2);
s = (double) cos(angle / 2);
i = uv.i * dSinHalf;
j = uv.j * dSinHalf;
k = uv.k * dSinHalf;
}



But that''s not the constructor you are using to create the result of the product is it? I would suggest that you modify the constructor that taks 4 doubles to normalize the result i.e. divide each of the components by the sqrt of the sum of the squares of the components (your Norm() function). I think you''ll find that this is where the quaternions are becoming denormalised and hence not representing the rotations you''d expect.

Share this post


Link to post
Share on other sites
jamessharpe,

I hope I understand that you mean I should have unit quaternions instead of unit vectors? See below; the problem seems to persist.

I have another point to make. If you have a flight simulator (I have only one by the name of Orbiter), the result of using global is like what you would normally expect when flying, but I expect it to be like rotating always w.r.t. the three global axes. If global is false then after you pitch a bit then bank, the camera will trace out a mystical curve and keeps rolling. Don't know what caused it; high school math does not include quaternions.

It is hard to impress on the effect unless with an executable. Maybe you can try http://personal.nbnet.nb.ca/daveg/opengl/quatdemo/index.html, I believe the effect is similar to the rotations without quats - very unsmooth with pauses and rolls, not at all like an aircraft.

Please refer to:


CQuaternion::CQuaternion(CVector3 v, double angle)
{
CVector3 uv = v.Unit();
double dSinHalf = (double) sin(angle / 2);
s = (double) cos(angle / 2);
i = uv.i * dSinHalf;
j = uv.j * dSinHalf;
k = uv.k * dSinHalf;
*this = this->Unit();
}

const CQuaternion CQuaternion::Unit() const
{
return *this / this->Norm();
}

void CCamera::Rotate(double i, double j, double k, double angle, bool global)
{
if (global)
m_q = m_q * CQuaternion(CVector3(i, j, k), angle);
else
m_q = m_q * CQuaternion(m_ia * i + m_ja * j + m_ka * k, angle);
m_ia = m_q.Rotate(I_AXIS);
m_ja = m_q.Rotate(J_AXIS);
m_ka = m_q.Rotate(K_AXIS);
}


singulab

[edited by - singulab on January 10, 2004 7:59:30 AM]

Share this post


Link to post
Share on other sites
Actually, should the code be like


void CCamera::Rotate(double i, double j, double k, double angle, bool global){
if (global)
m_q = m_q * CQuaternion(m_ia * i + m_ja * j + m_ka * k, angle);
else
{
CVector3 v(i, j, k);
// rotate the internal vector to global representation

// IMPORTANT: I don''t know how to do this. The only method I have

// is to solve 3 simultaneous equations, which I cannot handle.

v = m_q.Conjugate().Rotate(v);
// Maybe it should be

// v = m_q.Inverse().Rotate(v);

m_q = m_q * CQuaternion(v, angle);
}
m_ia = m_q.Rotate(I_AXIS);
m_ja = m_q.Rotate(J_AXIS);
m_ka = m_q.Rotate(K_AXIS);
}


It is just a wild guess that doesn''t work but something similar may work.

Share this post


Link to post
Share on other sites