//===========================================================================//
// File:	affnmtrx.hh                                                         //
// Project: MUNGA           Brick: Math Library                              //
// Contents: Interface specifications for Affine matrices                    //
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
// 11/20/94  JMA  Initial coding.                                            //
//---------------------------------------------------------------------------//
// Copyright (C) 1994-1995, Virtual World Entertainment, Inc.                //
// All Rights reserved worldwide                                             //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL               //
//===========================================================================//

#pragma once

#include "Stuff.hpp"
#include "Point3D.hpp"

namespace Stuff {class AffineMatrix4D;}

#if !defined(Spew)
	void
		Spew(
			const char* group,
			const Stuff::AffineMatrix4D& matrix
		);
#endif

namespace Stuff {

	class Origin3D;
	class EulerAngles;
	class UnitQuaternion;
	class YawPitchRoll;

	//~~~~~~~~~~~~~~~~~~~~~~~~~~ AffineMatrix4D ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

	class AffineMatrix4D
	{
	 public:
		static const AffineMatrix4D
			Identity;

		Scalar
			entries[12];

		//
		// Constructors
		//
		AffineMatrix4D()
			{}
		AffineMatrix4D&
			BuildIdentity();
		explicit AffineMatrix4D(int)
			{BuildIdentity();}
		AffineMatrix4D(const AffineMatrix4D &m)
			{*this = m;}
		explicit AffineMatrix4D(const Origin3D &p)
			{*this = p;}
		explicit AffineMatrix4D(const Matrix4D &m)
			{*this = m;}
		explicit AffineMatrix4D(const EulerAngles &angles)
			{*this = angles;}
		explicit AffineMatrix4D(const YawPitchRoll &angles)
			{*this = angles;}
		explicit AffineMatrix4D(const UnitQuaternion &q)
			{*this = q;}
		explicit AffineMatrix4D(const Point3D &p)
			{*this = p;}

		//
		// Assignment Operators
		//
		AffineMatrix4D&
			operator=(const AffineMatrix4D &m)
				{
					Check_Pointer(this); Check_Object(&m);
					memcpy(entries, m.entries, sizeof(m.entries)); return *this;
				}
		AffineMatrix4D&
			operator=(const Origin3D &p);
		AffineMatrix4D&
			operator=(const Matrix4D &m);
		AffineMatrix4D&
			operator=(const EulerAngles &angles);
		AffineMatrix4D&
			operator=(const YawPitchRoll &angles);
		AffineMatrix4D&
			operator=(const UnitQuaternion &q);
		AffineMatrix4D&
			operator=(const Point3D &p);

		AffineMatrix4D&
			BuildRotation(const EulerAngles &angles);
		AffineMatrix4D&
			BuildRotation(const YawPitchRoll &angles);
		AffineMatrix4D&
			BuildRotation(const UnitQuaternion &q);
		AffineMatrix4D&
			BuildRotation(const Vector3D &angles);
		AffineMatrix4D&
			BuildTranslation(const Point3D &p)
				{
					Check_Pointer(this); Check_Object(&p);
					(*this)(W_Axis, X_Axis) = p.x;
					(*this)(W_Axis, Y_Axis) = p.y;
					(*this)(W_Axis, Z_Axis) = p.z;
					return *this;
				}
		AffineMatrix4D&
			BuildLookAt(const YawPitchRange& rotation);
		AffineMatrix4D&
			BuildLookFrom(const YawPitchRange& rotation);

		//
		// Comparison operators
		//
		friend bool
			Close_Enough(
				const AffineMatrix4D &m1,
				const AffineMatrix4D &m2,
				Scalar e=SMALL
			);
		bool
			operator==(const AffineMatrix4D& a) const
				{return Close_Enough(*this,a,SMALL);}
		bool
			operator!=(const AffineMatrix4D& a) const
				{return !Close_Enough(*this,a,SMALL);}

		//
		// Index operators
		//
		Scalar&
			operator()(size_t row,size_t column)
				{
					Check_Pointer(this);
					Verify(static_cast<unsigned>(row) <= W_Axis);
					Verify(static_cast<unsigned>(column) <= Z_Axis);
					return entries[(column<<2)+row];
				}
		const Scalar&
			operator ()(size_t row,size_t column) const
				{
					Check_Pointer(this);
					Verify(static_cast<unsigned>(row) <= W_Axis);
					Verify(static_cast<unsigned>(column) <= Z_Axis);
					return entries[(column<<2)+row];
				}

		//
		// Axis Manipulation functions
		//
		void
			GetLocalForwardInWorld(Vector3D *v) const
		{
			Check_Object(this); Check_Pointer(v);
			v->x = APPLY_FORWARD_SIGN((*this)(FORWARD_AXIS, X_Axis));
			v->y = APPLY_FORWARD_SIGN((*this)(FORWARD_AXIS, Y_Axis));
			v->z = APPLY_FORWARD_SIGN((*this)(FORWARD_AXIS, Z_Axis));
		}
		void
			GetWorldForwardInLocal(Vector3D *v) const
		{
			Check_Object(this); Check_Pointer(v);
			v->x = APPLY_FORWARD_SIGN((*this)(X_Axis, FORWARD_AXIS));
			v->y = APPLY_FORWARD_SIGN((*this)(Y_Axis, FORWARD_AXIS));
			v->z = APPLY_FORWARD_SIGN((*this)(Z_Axis, FORWARD_AXIS));
		}

		void
			GetLocalBackwardInWorld(Vector3D *v) const
		{
			Check_Object(this); Check_Pointer(v);
			v->x = APPLY_BACKWARD_SIGN((*this)(BACKWARD_AXIS, X_Axis));
			v->y = APPLY_BACKWARD_SIGN((*this)(BACKWARD_AXIS, Y_Axis));
			v->z = APPLY_BACKWARD_SIGN((*this)(BACKWARD_AXIS, Z_Axis));
		}
		void
			GetWorldBackwardInLocal(Vector3D *v) const
		{
			Check_Object(this); Check_Pointer(v);
			v->x = APPLY_BACKWARD_SIGN((*this)(X_Axis, BACKWARD_AXIS));
			v->y = APPLY_BACKWARD_SIGN((*this)(Y_Axis, BACKWARD_AXIS));
			v->z = APPLY_BACKWARD_SIGN((*this)(Z_Axis, BACKWARD_AXIS));
		}

		void
			GetLocalRightInWorld(Vector3D *v) const
		{
			Check_Object(this); Check_Pointer(v);
			v->x = APPLY_RIGHT_SIGN((*this)(RIGHT_AXIS, X_Axis));
			v->y = APPLY_RIGHT_SIGN((*this)(RIGHT_AXIS, Y_Axis));
			v->z = APPLY_RIGHT_SIGN((*this)(RIGHT_AXIS, Z_Axis));
		}
		void
			GetWorldRightInLocal(Vector3D *v) const
		{
			Check_Object(this); Check_Pointer(v);
			v->x = APPLY_RIGHT_SIGN((*this)(X_Axis, RIGHT_AXIS));
			v->y = APPLY_RIGHT_SIGN((*this)(Y_Axis, RIGHT_AXIS));
			v->z = APPLY_RIGHT_SIGN((*this)(Z_Axis, RIGHT_AXIS));
		}

		void
			GetLocalLeftInWorld(Vector3D *v) const
		{
			Check_Object(this); Check_Pointer(v);
			v->x = APPLY_LEFT_SIGN((*this)(LEFT_AXIS, X_Axis));
			v->y = APPLY_LEFT_SIGN((*this)(LEFT_AXIS, Y_Axis));
			v->z = APPLY_LEFT_SIGN((*this)(LEFT_AXIS, Z_Axis));
		}
		void
			GetWorldLeftInLocal(Vector3D *v) const
		{
			Check_Object(this); Check_Pointer(v);
			v->x = APPLY_LEFT_SIGN((*this)(X_Axis, LEFT_AXIS));
			v->y = APPLY_LEFT_SIGN((*this)(Y_Axis, LEFT_AXIS));
			v->z = APPLY_LEFT_SIGN((*this)(Z_Axis, LEFT_AXIS));
		}

		void
			GetLocalUpInWorld(Vector3D *v) const
		{
			Check_Object(this); Check_Pointer(v);
			v->x = APPLY_UP_SIGN((*this)(UP_AXIS, X_Axis));
			v->y = APPLY_UP_SIGN((*this)(UP_AXIS, Y_Axis));
			v->z = APPLY_UP_SIGN((*this)(UP_AXIS, Z_Axis));
		}
		void
			GetWorldUpInLocal(Vector3D *v) const
		{
			Check_Object(this); Check_Pointer(v);
			v->x = APPLY_UP_SIGN((*this)(X_Axis, UP_AXIS));
			v->y = APPLY_UP_SIGN((*this)(Y_Axis, UP_AXIS));
			v->z = APPLY_UP_SIGN((*this)(Z_Axis, UP_AXIS));
		}

		void
			GetLocalDownInWorld(Vector3D *v) const
		{
			Check_Object(this); Check_Pointer(v);
			v->x = APPLY_DOWN_SIGN((*this)(DOWN_AXIS, X_Axis));
			v->y = APPLY_DOWN_SIGN((*this)(DOWN_AXIS, Y_Axis));
			v->z = APPLY_DOWN_SIGN((*this)(DOWN_AXIS, Z_Axis));
		}
		void
			GetWorldDownInLocal(Vector3D *v) const
		{
			Check_Object(this); Check_Pointer(v);
			v->x = APPLY_DOWN_SIGN((*this)(X_Axis, DOWN_AXIS));
			v->y = APPLY_DOWN_SIGN((*this)(Y_Axis, DOWN_AXIS));
			v->z = APPLY_DOWN_SIGN((*this)(Z_Axis, DOWN_AXIS));
		}

		//
		// Matrix Multiplication
		//
		inline AffineMatrix4D&
			Multiply(
				const AffineMatrix4D& Source1,
				const AffineMatrix4D& Source2
			)
		{
			Check_Pointer(this);
			Check_Object(&Source1);
			Check_Object(&Source2);
			Verify(this != &Source1);
			Verify(this != &Source2);

#if USE_ASSEMBLER_CODE
				Scalar *f = entries;
				_asm {
					mov         edx, Source1.entries
					push        esi
					mov         esi, Source2.entries
					
					mov         eax, f

					fld         dword ptr [edx]			//	s1[0][0]
					fmul        dword ptr [esi]			//	s2[0][0]	M0,1

					fld         dword ptr [edx+010h]	//	s1[0][1]
					fmul        dword ptr [esi+4]		//	s2[1][0]	M0,2
					
					fld         dword ptr [edx+020h]	//	s1[0][2]
					fmul        dword ptr [esi+8]		//	s2[2][0]	M0,3

					fxch		st(2)
					faddp       st(1),st				//				A0,1

					fld         dword ptr [edx+4]		//	s1[1][0]
					fmul        dword ptr [esi]			//	s2[0][0]	M1,1

					fxch		st(2)
					faddp       st(1),st				//				A0,2

					fld         dword ptr [edx+14h]		//	s1[1][1]
					fmul        dword ptr [esi+4]		//	s2[1][0]	M1,2
					
					fxch		st(1)
					fstp        dword ptr [eax]			//	[0][0]		S0

					fld         dword ptr [edx+24h]		//	s1[1][2]
					fmul        dword ptr [esi+8]		//	s2[2][0]	M1,3

					fxch		st(2)
					faddp       st(1),st				//				A1,1

					fld         dword ptr [edx+8]		//	s1[2][0]
					fmul        dword ptr [esi]			//	s2[0][0]	M2,1

					fxch		st(2)
					faddp       st(1),st				//				A1,2

					fld         dword ptr [edx+018h]	//	s1[2][1]
					fmul        dword ptr [esi+4]		//	s2[1][0]	M2,2

					fxch		st(1)
					fstp        dword ptr [eax+4]		//	[1][0]		S1

					fld         dword ptr [edx+28h]		//	s1[2][2]
					fmul        dword ptr [esi+8]		//	s2[2][0]	M2,3
					
					fxch		st(2)
					faddp       st(1),st				//				A2,1

					fld         dword ptr [edx+0ch]		//	s1[3][0]
					fmul        dword ptr [esi]			//	s2[0][0]	M3,1

					fxch		st(2)
					faddp       st(1),st				//				A2,2

					fld         dword ptr [edx+1ch]		//	s1[3][1]
					fmul        dword ptr [esi+4]		//	s2[1][0]	M3,2

					fxch		st(1)
					fstp        dword ptr [eax+8]		//	[2][0]		S2

					fld         dword ptr [edx+2ch]		//	s1[3][2]
					fmul        dword ptr [esi+8]		//	s2[2][0]	M3,3

					fxch		st(2)
					faddp       st(1),st				//				A3,1

//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

					fld         dword ptr [edx]			//	s1[0][0]
					fmul        dword ptr [esi+010h]	//	s2[0][1]	M0,1

					fxch		st(2)
					faddp		st(1),st				//				A3,2

					fld         dword ptr [edx+010h]	//	s1[0][1]
					fmul        dword ptr [esi+014h]	//	s2[1][1]	M0,2

					fxch		st(1)
					fadd        dword ptr [esi+0Ch]		//	s2[3][0]	A3,3
					
					fld         dword ptr [edx+020h]	//	s1[0][2]
					fmul        dword ptr [esi+018h]	//	s2[2][1]	M0,3

					fxch		st(1)
					fstp        dword ptr [eax+0Ch]		//	[3][0]		S3

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv

					fxch		st(2)
					faddp       st(1),st				//				A0,1

					fld         dword ptr [edx+4]		//	s1[1][0]
					fmul        dword ptr [esi+010h]	//	s2[0][1]	M1,1

					fxch		st(2)
					faddp       st(1),st				//				A0,2

					fld         dword ptr [edx+014h]	//	s1[1][1]
					fmul        dword ptr [esi+014h]	//	s2[1][1]	M1,2
					
					fxch		st(1)
					fstp        dword ptr [eax+010h]	//	[0][1]		S0

					fld         dword ptr [edx+024h]	//	s1[1][2]
					fmul        dword ptr [esi+018h]	//	s2[2][1]	M1,3

					fxch		st(2)
					faddp       st(1),st				//				A1,1

					fld         dword ptr [edx+8]		//	s1[2][0]
					fmul        dword ptr [esi+010h]	//	s2[0][1]	M2,1

					fxch		st(2)
					faddp       st(1),st				//				A1,2

					fld         dword ptr [edx+018h]	//	s1[2][1]
					fmul        dword ptr [esi+014h]	//	s2[1][1]	M2,2

					fxch		st(1)
					fstp        dword ptr [eax+014h]	//	[1][1]		S1

					fld         dword ptr [edx+028h]	//	s1[2][2]
					fmul        dword ptr [esi+018h]	//	s2[2][1]	M2,3
					
					fxch		st(2)
					faddp       st(1),st				//				A2,1

					fld         dword ptr [edx+0ch]		//	s1[3][0]
					fmul        dword ptr [esi+010h]	//	s2[0][1]	M3,1

					fxch		st(2)
					faddp       st(1),st				//				A2,2

					fld         dword ptr [edx+01ch]	//	s1[3][1]
					fmul        dword ptr [esi+014h]	//	s2[1][1]	M3,2

					fxch		st(1)
					fstp        dword ptr [eax+018h]	//	[2][1]		S2

					fld         dword ptr [edx+02ch]	//	s1[3][2]
					fmul        dword ptr [esi+018h]	//	s2[2][1]	M3,3

					fxch		st(2)
					faddp       st(1),st				//				A3,1

//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

					fld         dword ptr [edx]			//	s1[0][0]
					fmul        dword ptr [esi+020h]	//	s2[0][2]	M0,1

					fxch		st(2)
					faddp		st(1),st				//				A3,2

					fld         dword ptr [edx+010h]	//	s1[0][1]
					fmul        dword ptr [esi+024h]	//	s2[1][2]	M0,2

					fxch		st(1)
					fadd        dword ptr [esi+01Ch]	//	s2[3][1]	A3,3
					
					fld         dword ptr [edx+020h]	//	s1[0][2]
					fmul        dword ptr [esi+028h]	//	s2[2][2]	M0,3

					fxch		st(1)
					fstp        dword ptr [eax+01Ch]	//	[3][1]		S3

//vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv

					fxch		st(2)
					faddp       st(1),st				//				A0,1

					fld         dword ptr [edx+4]		//	s1[1][0]
					fmul        dword ptr [esi+020h]	//	s2[0][2]	M1,1

					fxch		st(2)
					faddp       st(1),st				//				A0,2

					fld         dword ptr [edx+014h]	//	s1[1][1]
					fmul        dword ptr [esi+024h]	//	s2[1][2]	M1,2
					
					fxch		st(1)
					fstp        dword ptr [eax+020h]	//	[0][2]		S0

					fld         dword ptr [edx+024h]	//	s1[1][2]
					fmul        dword ptr [esi+028h]	//	s2[2][2]	M1,3

					fxch		st(2)
					faddp       st(1),st				//				A1,1

					fld         dword ptr [edx+8]		//	s1[2][0]
					fmul        dword ptr [esi+020h]	//	s2[0][2]	M2,1

					fxch		st(2)
					faddp       st(1),st				//				A1,2

					fld         dword ptr [edx+018h]	//	s1[2][1]
					fmul        dword ptr [esi+024h]	//	s2[1][2]	M2,2

					fxch		st(1)
					fstp        dword ptr [eax+024h]	//	[1][2]		S1

					fld         dword ptr [edx+028h]	//	s1[2][2]
					fmul        dword ptr [esi+028h]	//	s2[2][2]	M2,3
					
					fxch		st(2)
					faddp       st(1),st				//				A2,1

					fld         dword ptr [edx+0ch]		//	s1[3][0]
					fmul        dword ptr [esi+020h]	//	s2[0][2]	M3,1

					fxch		st(2)
					faddp       st(1),st				//				A2,2

					fld         dword ptr [edx+01ch]	//	s1[3][1]
					fmul        dword ptr [esi+024h]	//	s2[1][2]	M3,2

					fxch		st(1)
					fstp        dword ptr [eax+028h]	//	[2][2]		S2

					fld         dword ptr [edx+02ch]	//	s1[3][2]
					fmul        dword ptr [esi+028h]	//	s2[2][2]	M3,3

					fxch		st(2)
					faddp       st(1),st				//				A3,1

					faddp		st(1),st				//				A3,2
					fadd        dword ptr [esi+02Ch]	//	s2[3][2]	A3,3
					
					fstp        dword ptr [eax+02Ch]	//	[3][2]		S3

					pop         esi
				}
#else
			(*this)(0,0) =
				Source1(0,0)*Source2(0,0)
				 + Source1(0,1)*Source2(1,0)
				 + Source1(0,2)*Source2(2,0);
			(*this)(1,0) =
				Source1(1,0)*Source2(0,0)
				 + Source1(1,1)*Source2(1,0)
				 + Source1(1,2)*Source2(2,0);
			(*this)(2,0) =
				Source1(2,0)*Source2(0,0)
				 + Source1(2,1)*Source2(1,0)
				 + Source1(2,2)*Source2(2,0);
			(*this)(3,0) =
				Source1(3,0)*Source2(0,0)
				 + Source1(3,1)*Source2(1,0)
				 + Source1(3,2)*Source2(2,0)
				 + Source2(3,0);

			(*this)(0,1) =
				Source1(0,0)*Source2(0,1)
				 + Source1(0,1)*Source2(1,1)
				 + Source1(0,2)*Source2(2,1);
			(*this)(1,1) =
				Source1(1,0)*Source2(0,1)
				 + Source1(1,1)*Source2(1,1)
				 + Source1(1,2)*Source2(2,1);
			(*this)(2,1) =
				Source1(2,0)*Source2(0,1)
				 + Source1(2,1)*Source2(1,1)
				 + Source1(2,2)*Source2(2,1);
			(*this)(3,1) =
				Source1(3,0)*Source2(0,1)
				 + Source1(3,1)*Source2(1,1)
				 + Source1(3,2)*Source2(2,1)
				 + Source2(3,1);

			(*this)(0,2) =
				Source1(0,0)*Source2(0,2)
				 + Source1(0,1)*Source2(1,2)
				 + Source1(0,2)*Source2(2,2);
			(*this)(1,2) =
				Source1(1,0)*Source2(0,2)
				 + Source1(1,1)*Source2(1,2)
				 + Source1(1,2)*Source2(2,2);
			(*this)(2,2) =
				Source1(2,0)*Source2(0,2)
				 + Source1(2,1)*Source2(1,2)
				 + Source1(2,2)*Source2(2,2);
			(*this)(3,2) =
				Source1(3,0)*Source2(0,2)
				 + Source1(3,1)*Source2(1,2)
				 + Source1(3,2)*Source2(2,2)
				 + Source2(3,2);
#endif

			return *this;
		};

		AffineMatrix4D&
			operator*=(const AffineMatrix4D& m)
				{AffineMatrix4D temp(*this); return Multiply(temp,m);}

		//
		// Matrix Inversion
		//
		AffineMatrix4D&
			Invert(const AffineMatrix4D& Source);
		AffineMatrix4D&
			Invert()
				{AffineMatrix4D src(*this); return Invert(src);}

		//
		// Scaling, Rotation and Translation
		//
		AffineMatrix4D&
			Multiply(const AffineMatrix4D &m,const Vector3D &v);
		AffineMatrix4D&
			operator*=(const Vector3D &v)
				{AffineMatrix4D m(*this); return Multiply(m,v);}
		AffineMatrix4D&
			Multiply(const AffineMatrix4D &m,const UnitQuaternion &q);
		AffineMatrix4D&
			operator*=(const UnitQuaternion &q)
				{AffineMatrix4D m(*this); return Multiply(m,q);}
		AffineMatrix4D&
			Multiply(const AffineMatrix4D &m,const Point3D &p);
		AffineMatrix4D&
			operator*=(const Point3D& p)
				{AffineMatrix4D m(*this); return Multiply(m,p);}

		//
		// Miscellaneous Functions
		//
		Scalar
			Determinant() const;
		AffineMatrix4D&
			Solve();

		//
		// Support functions
		//
		#if !defined(Spew)
			friend void
				::Spew(
					const char* group,
					const AffineMatrix4D& matrix
				);
		#endif

		void
			TestInstance() const
				{}
		static bool
			TestClass();
	};

	inline Point3D&
		Point3D::operator=(const AffineMatrix4D& m)
			{
				Check_Pointer(this); Check_Object(&m);
				x = m(W_Axis, X_Axis);
				y = m(W_Axis, Y_Axis);
				z = m(W_Axis, Z_Axis);
				Check_Object(this);
				return *this;
			}

}

namespace MemoryStreamIO {

	inline Stuff::MemoryStream&
		Read(
			Stuff::MemoryStream *stream,
			Stuff::AffineMatrix4D *output
		)
			{return stream->ReadBytes(output, sizeof(*output));}
	inline Stuff::MemoryStream&
		Write(
			Stuff::MemoryStream *stream,
			const Stuff::AffineMatrix4D *input
		)
			{return stream->WriteBytes(input, sizeof(*input));}

}
