/* (c) 1999-2000 Tino Schwarze, see COPYING for details */
/**@pkg math*/
/**
 * class cQuaternion
 */

#ifndef cQuaternion_hh
#define cQuaternion_hh

#include "common.hh"
#include "cVertex.hh"		// aquire class cVertex
#include "cMatrix.hh"	// aquire class cMatrix

#if DEBUG
#include "iocMatrix.hh"
#endif

/**
 * A class for representing a quaternion and its operations.
 *
 * Contains a class representing a quaternion and allowing
 * operations like multiplying quaternions, transforming vertices
 * and generating a matrix from a quaternion.
 * @see cMatrix 
 * @see cVertex
 */
class cQuaternion {
public:
	/**
	 * default constructor
	 	*/
	cQuaternion () 
	{
		// initialize quaternion to identity
		SetIdentity ();
	};

	// use copy constructor provided by compiler

	// use destructor provided by compiler

	/**
	 * constructor w/ hard initialization of quaternion
	 	* @param cw scalar value of quaternion
		* @param cx x component of complex value
		* @param cy y component of complex value
		* @param cz z component of complex value
		*
		* @note used by GetInverse()
		*/
	cQuaternion (const GLfloat cw, 
				const GLfloat cx, const GLfloat cy, const GLfloat cz)
		: s(cw), v(cx, cy, cz) {};

	/**
	 * constructor w/ initialization
	 	* @param angle angle in degree
		* @param axis vector to rotate around
		*/
	cQuaternion (const GLfloat angle, const cVertex &axis)
	{
		SetRotation (angle, axis);
	}

// OPERATORS
	/**
	 * operator * for two quaternions (multiply two quaternions)
	 	* @param q quaternion to multiply with
		*
		* @return resulting cQuaternion object
		*/
	cQuaternion operator * (const cQuaternion &q) const 
	{
		// q1*q2 = (s1*s2 - v1*v2 , s1*v2 + s2*v1 + v1 x v2)
		return cQuaternion (s*q.s - v*q.v,
							q.v*s + v*q.s + v.CrossProd(q.v));
	}

	/**
	 * operator *= for quaternions (multiply two quaternions)
	 	* @param q quaternion to multiply with
		*
		* @return reference to cQuaternion
		*/
	cQuaternion &operator *= (const cQuaternion &q) 
	{
		GLfloat sn;

		sn = s*q.s - v*q.v;
		v = q.v*s + v*q.s + v.CrossProd(q.v);
		s = sn;
		return (*this);
	}

	/**
	 * operator * for vertices (applies quaternion to vertex)
	 	* @param v cVertex to transform
		*
		* @return the transformed vertex as cVertex object
		*/
	cVertex operator * (const cVertex &v) 
	{
	/*
		// create quaternion representation of vertex
		cQuaternion tmp (0, v);

		// v_rotated = q * v * q^-1
		//           = q * v * q'
		return ((*this) * tmp * GetInverse ()).v;
	*/
		// looks a lot easier than above and requires less
		// temporary cVertex objects but therefore a Matrix
		return GetAsMatrix()*v;
	}

// OPERATIONS
	/**
	 * set quaternion to identity (no transformation/rotation)
	 	*/
	void SetIdentity ()
	{
		s = 1.0;
		v.SetZero ();
	}

	/**
	 * set quaternion to represent given rotation
	 * (angle in degree)
	 	* @param angle in degree
		* @param axis to turn around
		*/
	void SetRotation (const GLfloat angle, const cVertex &axis) 
	{

		const GLfloat rad = M_PI*angle/180.0;

		s = cos(rad/2.0);
		// we need to make axis a unit vector to make some
		// things easier
		v = axis * (sin(rad/2.0)/axis.GetLength ());
	}

	/**
	 * normalize the quaternion (make it length 1)
	 	*/
	void Normalize ()
	{
		const GLfloat abs_value = GetLength ();

		s /= abs_value;
		v *= 1.0/abs_value;
	}

// ACCESS
	/**
	 * return s component of quaternion
	 	* @return GLfloat value of s
		*/
	GLfloat GetS () const 
	{
		return s;
	}

	/**
	 * return v component of quaternion
	 	* @return cVertex object with value of v
		*/
	cVertex GetV () const 
	{
		return v;
	}

	/**
	 * return "length" of quaternion
	 	* @returns GLfloat
		*/
	GLfloat GetLength () const
	{
		return sqrt (s*s + v*v);
	}

	/**
	 * return the inverse of the quaternion
	 	* @returns inverse quaternion
		*/
	cQuaternion GetInverse () {
		// caution: this _only_ works with unit quaternions!
		return cQuaternion (s, -v.GetX(), -v.GetY(), -v.GetZ());
	}

	/**
	 * return the matrix representation of the quaternion
	 	* @returns cMatrix object representing quaternion
		*/
	cMatrix GetAsMatrix () {
		cMatrix tmp;

		GetAsMatrix (tmp);

		return tmp;
	}

	/**
	 * shortcut for retrieving the quaternion in matrix representation
	 * prevents allocating temporary on stack
	 	* @param tmp reference to cMatrix object
	 	*/
	void GetAsMatrix (cMatrix &tmp)
	{
		// help optimizing a bit... <g>
		const GLfloat c2XY = 2*v.GetX ()*v.GetY ();
		const GLfloat c2XZ = 2*v.GetX ()*v.GetZ ();
		const GLfloat c2YZ = 2*v.GetY ()*v.GetZ ();
		const GLfloat c2sX = 2*s*v.GetX ();
		const GLfloat c2sY = 2*s*v.GetY ();
		const GLfloat c2sZ = 2*s*v.GetZ ();

		tmp.mMatrix[0][0] = sqr (s) + sqr (v.GetX ()) - sqr (v.GetY()) - sqr (v.GetZ ());
		tmp.mMatrix[0][1] = c2XY + c2sZ;
		tmp.mMatrix[0][2] = c2XZ - c2sY;
		tmp.mMatrix[0][3] = 0;

		tmp.mMatrix[1][0] = c2XY - c2sZ;
		tmp.mMatrix[1][1] = sqr(s) - sqr (v.GetX ()) + sqr (v.GetY ()) - sqr (v.GetZ ());
		tmp.mMatrix[1][2] = c2YZ + c2sX;
		tmp.mMatrix[1][3] = 0;

		tmp.mMatrix[2][0] = c2XZ + c2sY;
		tmp.mMatrix[2][1] = c2YZ - c2sX;
		tmp.mMatrix[2][2] = sqr (s) - sqr (v.GetX ()) - sqr (v.GetY ()) + sqr (v.GetZ ());
		tmp.mMatrix[2][3] = 0;

		tmp.mMatrix[3][0] = 0;
		tmp.mMatrix[3][1] = 0;
		tmp.mMatrix[3][2] = 0;
		tmp.mMatrix[3][3] = sqr (s) + sqr (v.GetX ()) + sqr (v.GetY ()) + sqr (v.GetZ ());

#if DEBUG
		cerr << "rotation matrix is " << tmp << endl;
#endif
	}


private:
	/* I chose the representation (s, v) = (scalar, complex[3]) 
	 * of the quaternion. */
	GLfloat s;
	cVertex v;

};

#endif	// ifndef cQuaternion_hh
/* vim:ts=4:smartindent:
*/

