//===========================================================================//
// File:	vector3d.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 "MemoryStream.hpp"
#include "Scalar.hpp"

namespace Stuff {class Vector3D;}

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

namespace Stuff {

	class AffineMatrix4D;
	class LinearMatrix4D;
	class Matrix4D;
	class Point3D;
	class UnitQuaternion;
	class YawPitchRange;

	enum Axes {
		X_Axis,
		Y_Axis,
		Z_Axis,
		W_Axis
	};

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// The following macros define MUNGAs base coordinate system
	//
	// Note that Yaw is always about the up/down axis, pitch is always about
	// the left/right axis, and Roll is always about the forward/back axis.  In
	// order to rotate about negative axes, the appropriate macros should be
	// uncommented
	//
//	#define LEFT_HANDED_COORDINATES

	#define UP_AXIS Stuff::Y_Axis
	#define DOWN_AXIS Stuff::Y_Axis
	#define FORWARD_AXIS Stuff::Z_Axis
	#define BACKWARD_AXIS Stuff::Z_Axis
	#define RIGHT_AXIS Stuff::X_Axis
	#define LEFT_AXIS Stuff::X_Axis

	#define PRIMARY_AXIS LEFT_AXIS
	#define SECONDARY_AXIS UP_AXIS
	#define TERTIARY_AXIS FORWARD_AXIS

//	#define YAW_ABOUT_DOWN			// Default Yaw is about Up
// #define PITCH_ABOUT_LEFT		// Default Pitch is about Right
//	#define ROLL_ABOUT_BACKWARD	// Default Roll is about Forward

	#define APPLY_UP_SIGN(y) (y)
	#define UP_X (0.0f)
	#define UP_Y (1.0f)
	#define UP_Z (0.0f)

	#define APPLY_FORWARD_SIGN(z) (z)
	#define FORWARD_X (0.0f)
	#define FORWARD_Y (0.0f)
	#define FORWARD_Z (1.0f)

	#define APPLY_RIGHT_SIGN(x) (-(x))
	#define RIGHT_X (-1.0f)
	#define RIGHT_Y (0.0f)
	#define RIGHT_Z (0.0f)

	#define APPLY_DOWN_SIGN(y) (-(y))
	#define DOWN_X (-UP_X)
	#define DOWN_Y (-UP_Y)
	#define DOWN_Z (-UP_Z)

	#define APPLY_BACKWARD_SIGN(z) (-(z))
	#define BACKWARD_X (-FORWARD_X)
	#define BACKWARD_Y (-FORWARD_Y)
	#define BACKWARD_Z (-FORWARD_Z)

	#define APPLY_LEFT_SIGN(x) (x)
	#define LEFT_X (-RIGHT_X)
	#define LEFT_Y (-RIGHT_Y)
	#define LEFT_Z (-RIGHT_Z)

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Vector3D ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

	class Vector3D
	{
	public:
		Scalar
			x,
			y,
			z;

		static const Vector3D
			Identity;
		static const Vector3D
			Forward;
		static const Vector3D
			Backward;
		static const Vector3D
			Up;
		static const Vector3D
			Down;
		static const Vector3D
			Left;
		static const Vector3D
			Right;

		//
		// Constructors
		//
		Vector3D()
			{}
		Vector3D(
			Scalar x,
			Scalar y,
			Scalar z
		):
			x(x),
			y(y),
			z(z)
				{}
		Vector3D(const Vector3D &v):
			x(v.x),
			y(v.y),
			z(v.z)
				{}
		explicit Vector3D(const UnitQuaternion &q)
			{*this = q;}
		explicit Vector3D(const YawPitchRange &p)
			{*this = p;}

		//
		// Assignment operators
		//
		Vector3D&
			operator=(const Vector3D &vector)
				{
					Check_Pointer(this); Check_Object(&vector);
					x = vector.x; y = vector.y; z = vector.z;
					return *this;
				}
		Vector3D&
			operator=(const UnitQuaternion &q);
		Vector3D&
			operator=(const YawPitchRange &p);

		//
		// "Close-enough" comparators
		//
		friend bool
			Small_Enough(
				const Vector3D &v,
				Scalar e=SMALL
			)
				{Check_Object(&v); return v.GetLengthSquared() <= e;}
		bool
			operator!() const
				{return Small_Enough(*this, SMALL);}

		friend bool
			Close_Enough(
				const Vector3D &v1,
				const Vector3D &v2,
				Scalar e=SMALL
			);
		bool
			operator==(const Vector3D& v) const
				{return Close_Enough(*this,v,SMALL);}
		bool
			operator!=(const Vector3D& v) const
				{return !Close_Enough(*this,v,SMALL);}

		void
			TestInstance() const
				{}

		//
		// Indexing operations
		//

		static int GetMemberCount(void)
			{
				return 3;
			}

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

		Scalar
			GetForwardComponent()
				{Check_Object(this); return APPLY_FORWARD_SIGN((*this)[FORWARD_AXIS]);}
		Scalar
			GetBackwardComponent()
				{Check_Object(this); return APPLY_BACKWARD_SIGN((*this)[BACKWARD_AXIS]);}
		Scalar
			GetUpComponent()
				{Check_Object(this); return APPLY_UP_SIGN((*this)[UP_AXIS]);}
		Scalar
			GetDownComponent()
				{Check_Object(this); return APPLY_DOWN_SIGN((*this)[DOWN_AXIS]);}
		Scalar
			GetRightComponent()
				{Check_Object(this); return APPLY_RIGHT_SIGN((*this)[RIGHT_AXIS]);}
		Scalar
			GetLeftComponent()
				{Check_Object(this); return APPLY_LEFT_SIGN((*this)[LEFT_AXIS]);}

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

		Vector3D&
			Add(
				const Vector3D& v1,
				const Vector3D& 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; return *this;
				}
		Vector3D&
			AddScaled(
				const Vector3D& v1,
				const Vector3D& v2,
				Scalar scale
			)
				{
					Check_Pointer(this); Check_Object(&v1); Check_Object(&v2);
					x = v1.x + v2.x*scale; y = v1.y + v2.y*scale;
					z = v1.z + v2.z*scale; return *this;
				}
		Vector3D&
			operator+=(const Vector3D& v)
				{return Add(*this,v);}

		Vector3D&
			Subtract(
				const Vector3D& v1,
				const Vector3D& 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; return *this;
				}
		Vector3D&
			operator-=(const Vector3D& v)
				{return Subtract(*this,v);}

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

		Vector3D&
			Cross(
				const Vector3D& v1,
				const Vector3D& v2
			)
				{
					Check_Pointer(this); Check_Object(&v1); Check_Object(&v2);
					Verify(this != &v1); Verify(this != &v2);
					#if defined(LEFT_HANDED_COORDINATES)
						x = v2.y*v1.z - v2.z*v1.y; y = v2.z*v1.x - v2.x*v1.z;
						z = v2.x*v1.y - v2.y*v1.x; return *this;
					#else
						x = v1.y*v2.z - v1.z*v2.y; y = v1.z*v2.x - v1.x*v2.z;
						z = v1.x*v2.y - v1.y*v2.x; return *this;
					#endif
				}

		Vector3D&
			Cross(
				const Vector3D& p1,	//	v1 = p1 - p2
				const Vector3D& p2,	//	v2 = p3 - p2
				const Vector3D& p3
			)
				{
					Check_Pointer(this); 
					Check_Object(&p1); Check_Object(&p2); Check_Object(&p3);
					Verify(this != &p1); Verify(this != &p2); Verify(this != &p3);
					#if defined(LEFT_HANDED_COORDINATES)
						x = (p3.y - p2.y)*(p1.z - p2.z) - (p3.z - p2.z)*(p1.y - p2.y);
						y = (p3.z - p2.z)*(p1.x - p2.x) - (p3.x - p2.x)*(p1.z - p2.z);
						z = (p3.x - p2.x)*(p1.y - p2.y) - (p3.y - p2.y)*(p1.x - p2.x);
						return *this;
					#else
						x = (p1.y - p2.y)*(p3.z - p2.z) - (p1.z - p2.z)*(p3.y - p2.y);
						y = (p1.z - p2.z)*(p3.x - p2.x) - (p1.x - p2.x)*(p3.z - p2.z);
						z = (p1.x - p2.x)*(p3.y - p2.y) - (p1.y - p2.y)*(p3.x - p2.x);
						return *this;
					#endif
				}

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

		Vector3D&
			Multiply(
				const Vector3D& v1,
				const Vector3D& 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; return *this;
				}
		Vector3D&
			operator*=(const Vector3D &v)
				{return Multiply(*this,v);}

		Vector3D&
			Divide(
				const Vector3D& 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; return *this;
				}
		Vector3D&
			operator/=(Scalar v)
				{return Divide(*this,v);}

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

		//
		// Transform operators
		//
		Vector3D&
			Multiply(
				const Vector3D &v,
				const AffineMatrix4D &m
			);
		Vector3D&
			operator*=(const AffineMatrix4D &m)
				{Vector3D src(*this); return Multiply(src,m);}
		Vector3D&
			MultiplyByInverse(
				const Vector3D &v,
				const LinearMatrix4D &m
			);

		//
		// Miscellaneous functions
		//
		Scalar
			GetLengthSquared() const
				{Check_Object(this); return x*x + y*y + z*z;}
		Scalar
			GetLength() const
				{return Sqrt(GetLengthSquared());}

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

		Vector3D&
			Normalize(const Vector3D &v)
				{
					Check_Object(this); Check_Object(&v);
					Scalar len = v.GetLength();
					Verify(!Small_Enough(len)); len = 1.0f / len;
					x = v.x*len; y = v.y*len; z = v.z*len; return *this;
				}

		Vector3D&
			NormalizeApproximate(const Vector3D &v)
				{
					Check_Object(this); Check_Object(&v);
					Scalar len = v.GetApproximateLength();
					Verify(!Small_Enough(len)); len = OneOverApproximate(len);
					x = v.x*len; y = v.y*len; z = v.z*len; return *this;
				}


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

		void Zero (void)
		{
			x = y = z = 0.0;
		}

		//
		// Template support
		//
		Vector3D&
			Lerp(
				const Vector3D& v1,
				const Vector3D& 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); return *this;
				}

		//
		// Support functions
		//
		#if !defined(Spew)
			friend void
				::Spew(
					const char* group,
					const Vector3D &vector
				);
		#endif
		static bool
			TestClass();
	};

	//~~~~~~~~~~~~~~~~~~~~~~~~~~ Vector3D functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

}

namespace MemoryStreamIO {

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

}
