/*
	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_MATRIX4X4_HPP
#define PRMATH_MATRIX4X4_HPP



namespace prmath
{

	struct Matrix4x4
	{
		/*
	    The prmath matrix convention:

		; offsets
        [ 0  1  2  3]
        [ 4  5  6  7]
        [ 8  9  0 11]
        [12 13 14 15]

		; scaling
        [sx -- -- --] (sx,sy,sz)
        [-- sy -- --]
        [-- -- sz --]
        [-- -- -- --]
    
    	; translation
        [-- -- -- --]
        [-- -- -- --]
        [-- -- -- --]
        [tx ty tz --] (tx,ty,tz)
    
    	; rotation
        [xx xy xz --] x-axis (xx,xy,xz)
        [yx yy yz --] y-axis (yx,yy,yz)
        [zx zy zz --] z-axis (zx,zy,zz)
        [-- -- -- --]
        
    	*/

    	// members
    	union
    	{
    		float	m16[16];
    		float	m44[4][4];
    	};

		// constructors
		Matrix4x4() {}
		Matrix4x4(float scale);
		Matrix4x4(const Matrix4x4& m);
		Matrix4x4(const Quaternion& q);

		// operators
		Matrix4x4		operator +  (const Matrix4x4& right) const;
		Matrix4x4		operator -  (const Matrix4x4& right) const;
		Matrix4x4		operator *  (const Matrix4x4& right) const;
		Matrix4x4&		operator += (const Matrix4x4& right);
		Matrix4x4&		operator -= (const Matrix4x4& right);
		Matrix4x4&		operator *= (const Matrix4x4& right);
		void			operator  = (const Matrix4x4& right);
		void			operator  = (const Quaternion& q);
inline	Vector4&		operator [] (int index);
inline	const Vector4&	operator [] (int index) const;

		// methods
		void			Identity();
		void			Scale(float sx, float sy, float sz);
		void			Translate(float tx, float ty, float tz);
		void			RotateX(float angle);
		void			RotateY(float angle);
		void			RotateZ(float angle);
		void			RotateXYZ(float yaw, float pitch, float roll);
		void			RotateEuler(const Vector3& anglevec, EulerOrder euler);
		void			RotateAngleAxis(float angle, const Vector3& axis);

		void			MultScale(float sx, float sy, float sz);
		void			MultTranslate(float sx, float sy, float sz);
		void			MultRotateX(float angle);
		void			MultRotateY(float angle);
		void			MultRotateZ(float angle);
		void			MultRotateXYZ(float yaw, float pitch, float roll);
		void			MultRotateEuler(const Vector3& anglevec, EulerOrder euler);
		void			MultMatrix4x4(const Matrix4x4& right);
		void			MultMatrix3x4(const Matrix4x4& right);
		void			MultMatrix3x3(const Matrix4x4& right);
		void			MultInverseMatrix3x3(const Matrix4x4& right);

		void			Transpose();
		void			Transpose(const Matrix4x4& m);
		void			Adjoint();
		void			Adjoint(const Matrix4x4& m);
		void			Inverse();
		void			Inverse(const Matrix4x4& m);
		void			Inverse(const Quaternion& quat);

		void			OrthoNormalize();
		void			Mirror(const Matrix4x4& transform, const Plane& plane);
		void			LookAt(const Vector3& target, const Vector3& view, const Vector3& up);

		float			GetDeterminant() const;
		Vector3			GetEuler(EulerOrder euler=EULER_XYZ) const;

inline	void			SetX(const Vector3& x);
inline	void			SetY(const Vector3& y);
inline	void			SetZ(const Vector3& z);
inline	void			SetT(const Vector3& t);
inline	Vector3&		GetX();
inline	Vector3&		GetY();
inline	Vector3&		GetZ();
inline	Vector3&		GetT();
inline	const Vector3&	GetX() const;
inline	const Vector3&	GetY() const;
inline	const Vector3&	GetZ() const;
inline	const Vector3&	GetT() const;

inline	void			Scale(const Vector3& scale);
inline	void			Translate(const Vector3& translate);
inline	void			MultScale(const Vector3& scale);
inline	void			MultTranslate(const Vector3& translate);
	};


	// operators

inline	void		operator *= (Vector3& v, const Matrix4x4& m);
inline	void		operator *= (Vector4& v, const Matrix4x4& m);
inline	Vector3		operator * (const Vector3& v, const Matrix4x4& m);
inline	Vector4		operator * (const Vector4& v, const Matrix4x4& m);


	// implementation

inline const Vector4& Matrix4x4::operator [] (int index) const
{
	assert( index>=0 && index<=3 );
	return reinterpret_cast<const Vector4*>(this)[index];
}

inline Vector4& Matrix4x4::operator [] (int index)
{
	assert( index>=0 && index<=3 );
	return reinterpret_cast<Vector4*>(this)[index];
}

inline void Matrix4x4::SetX(const Vector3& x)
{
	m44[0][0] = x.x;
	m44[0][1] = x.y;
	m44[0][2] = x.z;
}

inline void Matrix4x4::SetY(const Vector3& y)
{
	m44[1][0] = y.x;
	m44[1][1] = y.y;
	m44[1][2] = y.z;
}

inline void Matrix4x4::SetZ(const Vector3& z)
{
	m44[2][0] = z.x;
	m44[2][1] = z.y;
	m44[2][2] = z.z;
}

inline void Matrix4x4::SetT(const Vector3& t)
{
	m44[3][0] = t.x;
	m44[3][1] = t.y;
	m44[3][2] = t.z;
}

inline Vector3& Matrix4x4::GetX()
{
	return *reinterpret_cast<Vector3*>(m16+0);
}

inline Vector3& Matrix4x4::GetY()
{
	return *reinterpret_cast<Vector3*>(m16+4);
}

inline Vector3& Matrix4x4::GetZ()
{
	return *reinterpret_cast<Vector3*>(m16+8);
}

inline Vector3& Matrix4x4::GetT()
{
	return *reinterpret_cast<Vector3*>(m16+12);
}

inline const Vector3& Matrix4x4::GetX() const
{
	return *reinterpret_cast<const Vector3*>(m16+0);
}

inline const Vector3& Matrix4x4::GetY() const
{
	return *reinterpret_cast<const Vector3*>(m16+4);
}

inline const Vector3& Matrix4x4::GetZ() const
{
	return *reinterpret_cast<const Vector3*>(m16+8);
}

inline const Vector3& Matrix4x4::GetT() const
{
	return *reinterpret_cast<const Vector3*>(m16+12);
}

inline void Matrix4x4::Scale(const Vector3& scale)
{
	Scale(scale.x,scale.y,scale.z);
}

inline void Matrix4x4::Translate(const Vector3& translate)
{
	Translate(translate.x,translate.y,translate.z);
}

inline void Matrix4x4::MultScale(const Vector3& scale)
{
	MultScale(scale.x,scale.y,scale.z);
}

inline void Matrix4x4::MultTranslate(const Vector3& translate)
{
	MultTranslate(translate.x,translate.y,translate.z);
}

inline Vector3 operator * (const Vector3& v, const Matrix4x4& m)
{
	return Vector3(
		v.x*m.m44[0][0] + v.y*m.m44[1][0] + v.z*m.m44[2][0] + m.m44[3][0],
		v.x*m.m44[0][1] + v.y*m.m44[1][1] + v.z*m.m44[2][1] + m.m44[3][1],
		v.x*m.m44[0][2] + v.y*m.m44[1][2] + v.z*m.m44[2][2] + m.m44[3][2] );
}

inline void operator *= (Vector3& v, const Matrix4x4& m)
{
	v =	Vector3(
		v.x*m.m44[0][0] + v.y*m.m44[1][0] + v.z*m.m44[2][0] + m.m44[3][0],
		v.x*m.m44[0][1] + v.y*m.m44[1][1] + v.z*m.m44[2][1] + m.m44[3][1],
		v.x*m.m44[0][2] + v.y*m.m44[1][2] + v.z*m.m44[2][2] + m.m44[3][2] );
}

inline Vector4 operator * (const Vector4& v, const Matrix4x4& m)
{
	return Vector4(
		v.x*m.m44[0][0] + v.y*m.m44[1][0] + v.z*m.m44[2][0] + v.w*m.m44[3][0],
		v.x*m.m44[0][1] + v.y*m.m44[1][1] + v.z*m.m44[2][1] + v.w*m.m44[3][1],
		v.x*m.m44[0][2] + v.y*m.m44[1][2] + v.z*m.m44[2][2] + v.w*m.m44[3][2],
		v.x*m.m44[0][3] + v.y*m.m44[1][3] + v.z*m.m44[2][3] + v.w*m.m44[3][3] );
}

inline void operator *= (Vector4& v, const Matrix4x4& m)
{
	v = Vector4(
		v.x*m.m44[0][0] + v.y*m.m44[1][0] + v.z*m.m44[2][0] + v.w*m.m44[3][0],
		v.x*m.m44[0][1] + v.y*m.m44[1][1] + v.z*m.m44[2][1] + v.w*m.m44[3][1],
		v.x*m.m44[0][2] + v.y*m.m44[1][2] + v.z*m.m44[2][2] + v.w*m.m44[3][2],
		v.x*m.m44[0][3] + v.y*m.m44[1][3] + v.z*m.m44[2][3] + v.w*m.m44[3][3] );
}

} // namespace prmath



#endif