/*
	Twilight Prophecy 3D/Multimedia SDK
	A multi-platform development system for virtual reality and multimedia.

	Copyright (C) 1997-2001 by Twilight 3D Finland Oy Ltd.
*/
#ifndef PRMATH_QUATERNION_HPP
#define PRMATH_QUATERNION_HPP



namespace prmath
{
	
	struct Quaternion
	{
		// members
		float	x;
		float	y;
		float	z;
		float	w;
		
		// constructors
inline	Quaternion() {}
inline	Quaternion(float x, float y, float z, float w);
inline	Quaternion(const float v[]);
inline	Quaternion(const Quaternion& q);
inline	Quaternion(float angle, const Vector3& axis);
inline	Quaternion(const Matrix4x4& m);

		// operators
inline	Quaternion		operator +  () const;
inline	Quaternion		operator -  () const;
inline	Quaternion		operator +  (const Quaternion& q) const;
inline	Quaternion		operator -  (const Quaternion& q) const;
inline	Quaternion		operator *  (const Quaternion& q) const;
inline	Quaternion		operator *  (const float& s) const;
inline	Quaternion&		operator += (const Quaternion& q);
inline	Quaternion&		operator -= (const Quaternion& q);
inline	Quaternion&		operator *= (const Quaternion& q);
inline	Quaternion&		operator *= (const float& s);
inline	void			operator  = (const Quaternion& q);
		void			operator  = (const Matrix4x4& m);
inline	bool			operator == (const Quaternion& q) const;
inline	bool			operator != (const Quaternion& q) const;

		// methods
		void			SetAngleAxis(float angle, const Vector3& axis);
		void			GetAngleAxis(float& angle, Vector3& axis) const;
		void			SetEuler(const Vector3& anglevec, EulerOrder euler);
inline	float			Norm() const;
inline	float			Mod() const;
inline	void			Identity();
		void			Normalize();
inline	void			Conjugate();
inline	void			Inverse();
inline	void			Negate();
		void			Exp();
		void			Log();
		void			LnDif(const Quaternion& q);
	};


	// functions
	
inline	float			DotProduct(const Quaternion& a, const Quaternion& b);
inline	Quaternion		Lerp(const Quaternion& a, const Quaternion& b, float t);
		Quaternion		Slerp(const Quaternion& a, const Quaternion& b, float t);
		Quaternion		Slerp(const Quaternion& a, const Quaternion& b, float t, int spin);
inline	Quaternion		Squad(const Quaternion& p, const Quaternion& a, const Quaternion& b, const Quaternion& q, float t);


	// implementation

inline Quaternion::Quaternion(float qx, float qy, float qz, float qw)
: x(qx), y(qy), z(qz), w(qw)
{
}

inline Quaternion::Quaternion(const float v[])
: x(v[0]), y(v[1]), z(v[2]), w(v[3])
{
}

inline Quaternion::Quaternion(const Quaternion& q)
: x(q.x), y(q.y), z(q.z), w(q.w)
{
}

inline Quaternion::Quaternion(float angle, const Vector3& axis)
{
	SetAngleAxis(angle,axis);
}

inline Quaternion::Quaternion(const Matrix4x4& m)
{
	*this = m;
}

inline Quaternion Quaternion::operator + () const
{
	return *this;
}

inline Quaternion Quaternion::operator - () const
{
	return Quaternion(
		-x,
		-y,
		-z,
		-w );
}

inline Quaternion Quaternion::operator + (const Quaternion& q) const
{
	return Quaternion(
		x + q.x,
		y + q.y,
		z + q.z,
		w + q.w );
}

inline Quaternion Quaternion::operator - (const Quaternion& q) const
{
	return Quaternion(
		x - q.x,
		y - q.y,
		z - q.z,
		w - q.w );
}

inline Quaternion Quaternion::operator * (const Quaternion& q) const
{
	return Quaternion(
		w*q.x + x*q.w + y*q.z - z*q.y,
		w*q.y + y*q.w + z*q.x - x*q.z,
		w*q.z + z*q.w + x*q.y - y*q.x,
		w*q.w - x*q.x - y*q.y - z*q.z );
}

inline Quaternion Quaternion::operator * (const float& s) const
{
	return Quaternion(
		x * s,
		y * s,
		z * s,
		w * s );
}

inline Quaternion& Quaternion::operator += (const Quaternion& q)
{
	x += q.x;
	y += q.y; 
	z += q.z;
	w += q.w;
	return *this;
}

inline Quaternion& Quaternion::operator -= (const Quaternion& q)
{
	x -= q.x;
	y -= q.y; 
	z -= q.z;
	w -= q.w;
	return *this;
}

inline Quaternion& Quaternion::operator *= (const Quaternion& q)
{
	*this = Quaternion(
		w*q.x + x*q.w + y*q.z - z*q.y,
		w*q.y + y*q.w + z*q.x - x*q.z,
		w*q.z + z*q.w + x*q.y - y*q.x,
		w*q.w - x*q.x - y*q.y - z*q.z );
	return *this;
}

inline Quaternion& Quaternion::operator *= (const float& s)
{
	x *= s;
	y *= s;
	z *= s;
	w *= s;
	return *this;
}

inline void Quaternion::operator = (const Quaternion& q)
{
	x = q.x;
	y = q.y;
	z = q.z;
	w = q.w;
}

inline bool Quaternion::operator == (const Quaternion& q) const
{
	return 
		x == q.x &&
		y == q.y &&
		z == q.z &&
		w == q.w;
}

inline bool Quaternion::operator != (const Quaternion& q) const
{
	return 
		x != q.x ||
		y != q.y ||
		z != q.z ||
		w != q.w;
}

inline float Quaternion::Norm() const
{
	return x*x + y*y + z*z + w*w;
}

inline float Quaternion::Mod() const
{
	return (float)sqrt(x*x + y*y + z*z + w*w);
}

inline void Quaternion::Identity()
{
	x = y = z = 0;
	w = 1;
}

inline void Quaternion::Conjugate()
{
	x = -x;
	y = -y;
	z = -z;
}

inline void Quaternion::Inverse()
{
	float n = -1 / Norm();
	x *= n;
	y *= n;
	z *= n;
	w *= -n;
}

inline void Quaternion::Negate()
{
	float m = -1 / Mod();
	x *= m;
	y *= m;
	z *= m;
	w *= -m;
}

inline float DotProduct(const Quaternion& a, const Quaternion& b)
{
	return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w;
}

inline Quaternion Lerp(const Quaternion& a, const Quaternion& b, float t)
{
	return Quaternion(
		a.x + (b.x - a.x) * t,
		a.y + (b.y - a.y) * t,
		a.z + (b.z - a.z) * t,
		a.w + (b.w - a.w) * t );
}

inline Quaternion Squad(const Quaternion& p, const Quaternion& a, const Quaternion& b, const Quaternion& q, float t)
{
	return Slerp(
		Slerp(p,q,t,0),
		Slerp(a,b,t,0),
		2*t*(1-t),0 );
}

} // namespace prmath



#endif