//===========================================================================//
// File:	vector4d.hh                                                         //
// Project: MUNGA           Brick: Math Library                              //
// Contents: Interface specification for vector classes                      //
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
// 11/19/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 Vector4D;}

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

namespace Stuff {

	class AffineMatrix4D;
	class Matrix4D;

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Vector4D ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

	class Vector4D
	{
	 public:
		Scalar
			x,
			y,
			z,
			w;

		static const Vector4D
			Identity;

		//
		// Constructors
		//
		Vector4D()
			{}
		Vector4D(
			Scalar X,
			Scalar Y,
			Scalar Z,
			Scalar W)
				{x=X; y=Y; z=Z; w=W;}
		Vector4D(const Vector4D &v)
			{x=v.x; y=v.y; z=v.z; w=v.w;}
		explicit Vector4D(const Vector3D &v)
			{*this = v;}
		explicit Vector4D(const Point3D &p)
			{*this = p;}

		//
		// Assignment operators
		//
		Vector4D&
			operator=(const Vector4D &v)
				{
					Check_Pointer(this); Check_Object(&v);
					x = v.x; y = v.y; z = v.z; w = v.w; return *this;
				}
		Vector4D&
			operator=(const Vector3D &v)
				{
					Check_Pointer(this); Check_Object(&v);
					x = v.x; y = v.y; z = v.z; w = 0.0f; return *this;
				}
		Vector4D&
			operator=(const Point3D &p)
				{
					Check_Pointer(this); Check_Object(&p);
					x = p.x; y = p.y; z = p.z; w = 1.0f; return *this;
				}

		//
		// Index operators
		//
		const Scalar&
			operator[](size_t index) const
				{
					Check_Pointer(this);
					Verify(static_cast<unsigned>(index) <= W_Axis);
					return (&x)[index];
				}
		Scalar&
			operator[](size_t index)
				{
					Check_Pointer(this);
					Verify(static_cast<unsigned>(index) <= W_Axis);
					return (&x)[index];
				}

		friend bool
			Small_Enough(const Vector4D &v,Scalar e=SMALL);
		bool
			operator!() const
				{return Small_Enough(*this);}

		//
		// "Close-enough" comparison operators
		//
		friend bool
			Close_Enough(
				const Vector4D &v1,
				const Vector4D &v2,
				Scalar e=SMALL
			);
		bool
			operator==(const Vector4D& v) const
				{return Close_Enough(*this,v);}
		bool
			operator!=(const Vector4D& v) const
				{return !Close_Enough(*this,v);}

		//
		// The following operators all assume that this points to the destination
		// of the operation results
		//
		Vector4D&
			Negate(const Vector4D &v)
				{
					Check_Pointer(this); Check_Object(&v);
					x = -v.x; y = -v.y; z = -v.z; w = -v.w; return *this;
				}

		Vector4D&
			Add(
				const Vector4D& v1,
				const Vector4D& v2
			)
				{
					Check_Pointer(this); Check_Object(&v1); Check_Object(&v2);
					x = v1.x + v2.x; y = v1.y + v2.y; z = v1.z + v2.z;
					w = v1.w + v2.w; return *this;
				}
		Vector4D&
			operator+=(const Vector4D& v)
				{return Add(*this,v);}

		Vector4D&
			Subtract(
				const Vector4D& v1,
				const Vector4D& v2
			)
				{
					Check_Pointer(this); Check_Object(&v1); Check_Object(&v2);
					x = v1.x - v2.x; y = v1.y - v2.y; z = v1.z - v2.z;
					w = v1.w - v2.w; return *this;
				}
		Vector4D&
			operator-=(const Vector4D& v)
				{return Subtract(*this,v);}

		Scalar
			operator*(const Vector4D& v) const
				{Check_Object(this); return x*v.x + y*v.y + z*v.z + w*v.w;}

		Vector4D&
			Multiply(
				const Vector4D& v,
				Scalar scale
			)
				{
					Check_Pointer(this); Check_Object(&v);
					x = v.x * scale; y = v.y * scale; z = v.z * scale;
					w = v.w * scale; return *this;
				}
		Vector4D&
			operator*=(Scalar v)
				{return Multiply(*this,v);}

		Vector4D&
			Multiply(
				const Vector4D& v1,
				const Vector4D& v2
			)
				{
					Check_Pointer(this); Check_Object(&v1); Check_Object(&v2);
					x = v1.x * v2.x; y = v1.y * v2.y; z = v1.z * v2.z;
					w = v1.w * v2.w; return *this;
				}
		Vector4D&
			operator*=(const Vector4D &v)
				{return Multiply(*this,v);}

		Vector4D&
			Divide(
				const Vector4D& v,
				Scalar scale
			)
				{
					Check_Pointer(this); Check_Object(&v);
					Verify(!Small_Enough(scale)); scale = 1.0f / scale;
					x = v.x * scale; y = v.y * scale; z = v.z * scale;
					w = v.w * scale; return *this;
				}
		Vector4D&
			operator/=(Scalar v)
				{return Divide(*this,v);}

		Vector4D&
			Divide(
				const Vector4D& v1,
				const Vector4D& v2
			)
				{
					Check_Pointer(this); Check_Object(&v1); Check_Object(&v2);
					Verify(!Small_Enough(v1.x)); Verify(!Small_Enough(v1.y));
					Verify(!Small_Enough(v1.z)); Verify(!Small_Enough(v1.w));
					x = v1.x / v2.x; y = v1.y / v2.y; z = v1.z / v2.z; 
					w = v1.w / v2.w; return *this;
				}
		Vector4D&
			operator/=(const Vector4D &v)
				{return Divide(*this,v);}

		//
		//  Transforms
		//
		Vector4D&
			Multiply(
				const Vector4D &v,
				const AffineMatrix4D &m
			);
		Vector4D&
			operator*=(const AffineMatrix4D &M)
				{Vector4D src(*this); return Multiply(src,M);}
		Vector4D& Multiply(
			const Vector4D &v,
			const Matrix4D &m
		);
		Vector4D&
			operator*=(const Matrix4D &m)
				{Vector4D src(*this); return Multiply(src,m);}
		Vector4D& Multiply(
			const Vector3D &v,
			const Matrix4D &m
		);

		Vector4D& Multiply(
			const Point3D &v,
			const Matrix4D &m
		)
			{
				Check_Pointer(this);
				Check_Object(&v);
				Check_Object(&m);

#if USE_ASSEMBLER_CODE
				Scalar *f = &x;
				_asm {
		mov         edx, m
		
		mov         eax, v

		fld			dword ptr [eax]			//	v.x
		fld			dword ptr [eax+4]		//	v.y
		fld			dword ptr [eax+8]		//	v.z

		mov         eax, f

		fld         dword ptr [edx+34h]		//	m[1][3]
		fmul        st, st(2)				//	v.y
		
		fld         dword ptr [edx+38h]		//	m[2][3]
		fmul        st, st(2)				//	v.z

		fxch		st(1)
		fadd        dword ptr [edx+3Ch]		//	m[3][3]
		
		fld         dword ptr [edx+30h]		//	m[0][3]
		fmul        st, st(5)				//	v.x

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

		fld         dword ptr [edx+14h]		//	m[1][1]
		fmul        st, st(4)				//	v.y

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

		fld         dword ptr [edx+18h]		//	m[2][1]
		fmul        st, st(3)				//	v.z
		
		fxch		st(1)
		fstp        dword ptr [eax+0Ch]		//	w

		fadd        dword ptr [edx+1Ch]		//	m[3][1]

		fld         dword ptr [edx+10h]		//	m[0][1]
		fmul        st, st(5)				//	v.x

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

		fld         dword ptr [edx+24h]		//	m[1][2]
		fmul        st, st(4)				//	v.y

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

		fld         dword ptr [edx+28h]		//	m[2][2]
		fmul        st, st(3)				//	v.z
		
		fxch		st(1)
		fstp        dword ptr [eax+4]		//	y

		fadd        dword ptr [edx+2Ch]		//	m[3][2]
		
		fld         dword ptr [edx+20h]		//	m[0][2]
		fmul        st, st(5)				//	v.x

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

		fld         dword ptr [edx+4]		//	m[1][0]
		fmul        st, st(4)				//	v.y

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

		fld         dword ptr [edx+8]		//	m[2][0]
		fmul        st, st(3)				//	v.z

		fxch		st(1)
		fstp        dword ptr [eax+8]		//	z

		fadd        dword ptr [edx+0Ch]		//	m[3][0]
		
		fld         dword ptr [edx]			//	m[0][0]
		fmul        st, st(5)				//	v.x
		
		fxch		st(2)
		faddp       st(1),st

		faddp       st(1),st

//	get rid of x, y, z
		fstp		st(1)
		fstp		st(1)
		fstp		st(1)

		fstp        dword ptr [eax]			//	x

	}
#else
				x = v.x*m(0,0) + v.y*m(1,0) + v.z*m(2,0) + m(3,0);
				y = v.x*m(0,1) + v.y*m(1,1) + v.z*m(2,1) + m(3,1);
				z = v.x*m(0,2) + v.y*m(1,2) + v.z*m(2,2) + m(3,2);
				w = v.x*m(0,3) + v.y*m(1,3) + v.z*m(2,3) + m(3,3);
#endif
				return *this;
			}

		Vector4D& MultiplySetClip(
			const Point3D &v,
			const Matrix4D &m,
			int *clipper
		);
/*
			{
				Check_Pointer(this);
				Check_Object(&v);
				Check_Object(&m);

#if USE_ASSEMBLER_CODE
				Scalar *f = &x;
				_asm {
		mov         edx, m
		
		mov         eax, v

		fld			dword ptr [eax]			//	v.x
		fld			dword ptr [eax+4]		//	v.y
		fld			dword ptr [eax+8]		//	v.z

		mov         eax, f

		fld         dword ptr [edx+34h]		//	m[1][3]
		fmul        st, st(2)				//	v.y
		
		fld         dword ptr [edx+38h]		//	m[2][3]
		fmul        st, st(2)				//	v.z

		fxch		st(1)
		fadd        dword ptr [edx+3Ch]		//	m[3][3]
		
		fld         dword ptr [edx+30h]		//	m[0][3]
		fmul        st, st(5)				//	v.x

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

		fld         dword ptr [edx+14h]		//	m[1][1]
		fmul        st, st(4)				//	v.y

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

		fld         dword ptr [edx+18h]		//	m[2][1]
		fmul        st, st(3)				//	v.z
		
		fxch		st(1)
		fstp        dword ptr [eax+0Ch]		//	w

		fadd        dword ptr [edx+1Ch]		//	m[3][1]

		fld         dword ptr [edx+10h]		//	m[0][1]
		fmul        st, st(5)				//	v.x

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

		fld         dword ptr [edx+24h]		//	m[1][2]
		fmul        st, st(4)				//	v.y

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

		fld         dword ptr [edx+28h]		//	m[2][2]
		fmul        st, st(3)				//	v.z
		
		fxch		st(1)
		fstp        dword ptr [eax+4]		//	y

		fadd        dword ptr [edx+2Ch]		//	m[3][2]
		
		fld         dword ptr [edx+20h]		//	m[0][2]
		fmul        st, st(5)				//	v.x

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

		fld         dword ptr [edx+4]		//	m[1][0]
		fmul        st, st(4)				//	v.y

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

		fld         dword ptr [edx+8]		//	m[2][0]
		fmul        st, st(3)				//	v.z

		fxch		st(1)
		fstp        dword ptr [eax+8]		//	z

		fadd        dword ptr [edx+0Ch]		//	m[3][0]
		
		fld         dword ptr [edx]			//	m[0][0]
		fmul        st, st(5)				//	v.x
		
		fxch		st(2)
		faddp       st(1),st

		faddp       st(1),st

//	get rid of x, y, z
		fstp		st(1)
		fstp		st(1)
		fstp		st(1)

		fstp        dword ptr [eax]			//	x

	}
#else
				x = v.x*m(0,0) + v.y*m(1,0) + v.z*m(2,0) + m(3,0);
				y = v.x*m(0,1) + v.y*m(1,1) + v.z*m(2,1) + m(3,1);
				z = v.x*m(0,2) + v.y*m(1,2) + v.z*m(2,2) + m(3,2);
				w = v.x*m(0,3) + v.y*m(1,3) + v.z*m(2,3) + m(3,3);
#endif

				clipper = 0;

				if(w <= z)
				{
					clipper |= 32;
				}

				if(z < 0.0f)
				{
					clipper |= 16;
				}

				if(x < 0.0f)
				{
					clipper |= 8;
				}

				if(w < x)
				{
					clipper |= 4;
				}

				if(y < 0.0f)
				{
					clipper |= 2;
				}

				if(w < y)
				{
					clipper |= 1;
				}

				return *this;
			}
*/
		//
		// Support functions
		//
		Scalar
			GetLengthSquared() const
				{return operator*(*this);}
		Scalar
			GetLength() const
				{return Sqrt(GetLengthSquared());}

		Scalar
			GetApproximateLength() const
				{return SqrtApproximate(GetLengthSquared());}

		Vector4D&
			Combine(
				const Vector4D& v1,
				Scalar t1,
				const Vector4D& v2,
				Scalar t2
			)
				{
					Check_Pointer(this); Check_Object(&v1); Check_Object(&v2);
					x = v1.x*t1 + v2.x*t2; y = v1.y*t1 + v2.y*t2;
					z = v1.z*t1 + v2.z*t2; w = v1.w*t1 + v2.w*t2;
					return *this;
				}

		Vector4D&
			Lerp(
				const Vector4D& v1,
				const Vector4D& v2,
				Scalar t
			)
				{
					Check_Pointer(this); Check_Object(&v1); Check_Object(&v2);
					x = v1.x + t*(v2.x-v1.x); y = v1.y + t*(v2.y-v1.y);
					z = v1.z + t*(v2.z-v1.z); w = v1.w + t*(v2.w-v1.w);
					return *this;
				}

		#if !defined(Spew)
			friend void
				::Spew(
					const char* group,
					const Vector4D& vector
				);
		#endif
		void
			TestInstance() const
				{}
		static bool
			TestClass();
	};

}
