/****************************************************************************************


		Transforms.h

		Copyright: (c) 2001 Eovia Corp. All rights reserved.

		Author:	Ray Dream
		Date:	3/9/98

		Revision History

		when		who			what & why
		==========	==========	======================================================
		5/20/04		Jeremy		Added GetComponents
		1/20/99		Joshua		Changed default values of constrainAngle to kRealZero
		11/23.98	Eml			Moved Time Based stuff to AnimatedTransform.cpp/h in Animation extension
		7/14/98		Joshua		Made changes to allow use in an extension

*****************************************************************************************/
#ifndef __Transforms__
#define __Transforms__

#if CP_PRAGMA_ONCE
#pragma once
#endif

#include "Plane.h"
#include "Matrix33.h"
#include "Vector2.h"
#include "Vector3.h"

#include "TransformsFwd.h"

class  TBBox3D;
class  TPlane;
class  TTreeTransform;


///Euler angles rotation order
enum EEulerAnglesOrder
{	
	kXYZOrder,	//The order used by GetPhyThetaPsy (Matrix= Rz*Ry*Rx: rotate in local x, in global Z and in funky Y ;-)
	kXZYOrder,
	kYXZOrder,
	kYZXOrder,
	kZYXOrder,  //the "order switch" between those last 2 values is intented (matches the old code in the the custom constraint).
	kZXYOrder,
};



/// \addtogroup Data
/// \{


///	TTransform3D is a minimal representation of a 3D Transform.
///
/// x'= fRotationAndScale*x + fTranslation
///
template<class T> class TTransform3DTemplate : public TMCObject1
{
public:
	inline TTransform3DTemplate() : fRotationAndScale(TMatrix33Template<T>::kIdentity), fTranslation(TVector3Template<T>::kZero) { }
	
	template<class U>
	explicit inline TTransform3DTemplate(const TTransform3DTemplate<U>& original)
	: fRotationAndScale(original.fRotationAndScale), fTranslation(original.fTranslation) {}

	inline TVector3Template<T> 	TransformPoint	(const TVector3Template<T> &PP) const;
	inline TVector3Template<T> 	TransformVector	(const TVector3Template<T> &VV) const;

	TTransform3DTemplate<T>	operator*(const TTransform3DTemplate<T> &transform) const;

	void							GetInverse(TTransform3DTemplate<T>& outInverse) const;
	inline TTransform3DTemplate<T>	GetInverse() const { TTransform3DTemplate<T> res; GetInverse(res); return res;}
	void							GetComponents(TMatrix33Template<T>& outRotation, TVector3Template<T>& outTranslation, TVector3Template<T>& outScaling) const;

	boolean				IsEqual(const TTransform3DTemplate<T>& transform, const real& zeroTolerance) const;
	boolean				IsEqualRelative(const TTransform3DTemplate<T>& transform, const real& zeroTolerance = 0) const;
	void				Reset();
	boolean				IsDirect() const;
	boolean				IsIdentity() const;

public:
	TMatrix33Template<T>	fRotationAndScale;	///< Matrix
	TVector3Template<T>		fTranslation;		///< Vector from parent 0,0,0 to local 0,0,0
};


/// \}

template<class T> inline TVector3Template<T> TTransform3DTemplate<T>::TransformPoint(const TVector3Template<T> &PP) const
{
	TVector3d res;
	for (int32 ii = 0; ii < 3; ii++)
	{
		res[ii] = fRotationAndScale[ii][0] * PP[0] + fRotationAndScale[ii][1] * PP[1] + fRotationAndScale[ii][2] * PP[2] + fTranslation[ii];
	}
	return res;
}

template<class T> inline TVector3Template<T> TTransform3DTemplate<T>::TransformVector(const TVector3Template<T> &VV) const
{
	TVector3d res;
	for (int32 ii = 0; ii < 3; ii++)
	{
		res[ii] = fRotationAndScale[ii][0] * VV[0] + fRotationAndScale[ii][1] * VV[1] + fRotationAndScale[ii][2] * VV[2];
	}
	return res;
}


/// \addtogroup Data
/// \{

///	TTransform3DComponents is a componentized representation of a 3D Transform.
///
/// x'= fRotation * (fScalingP*Diagonal(fScaling)*fScalingP') * x + fTranslation
///
/// where fRotation is an orthonormal matrix
///
class TTransform3DComponents : public TMCObject1
{
public:
	inline TTransform3DComponents() : fRotation(TMatrix33::kIdentity), fScalingP(TMatrix33::kIdentity),
		fScaling(TVector3::kOnes), fTranslation(TVector3::kZero), fHasScalingP(false) { }
	
	// decompose a TTransform3D
	void		Decompose(const TTransform3D& transform);	// this does a svd (costly)
	void		DecomposeOrthogonal(const TTransform3D& transform);

	void		GetTransform3D(TTransform3D& transform) const;
	void		GetTreeTransform(TTreeTransform& transform) const;

	// accessor
	const TMatrix33&	GetRotation() const { return fRotation; }
	const TVector3&		GetXYZScaling() const { return fScaling; }
	TMatrix33			GetScaling() const;
	const TVector3&		GetTranslation() const { return fTranslation; }

public:
	TMatrix33	fRotation;
	TMatrix33	fScalingP;
	boolean		fHasScalingP;
	TVector3	fScaling;
	TVector3	fTranslation;
};

/// \}



/// \addtogroup Data
/// \{


/// TTreeTransform is the full representation of a tree's transform
///
/// The formula to get a transform 3d from a tree transform depends on
/// its mode. \see TTreeTransform::ETreeTransformMode
///
class TTreeTransform : public TMCObject
{
public:
	/// Tr   :	translation
	/// Rot  :	rotation
	/// S    :	scaling
	/// Thp  :	hot point translation
	/// Ori  :	orientation
	enum ETreeTransformMode
	{
		kCarrara,	///< M = Tr * Rot * S * Ori * Thp
		kPoser,		///< M = Tr * (Ori * Thp)^-1 * Rot * S * (Ori * Thp)
	};

	class TRotation : public TMCObject
	{
	public:
		enum EMaster
		{
			kEuler,
			kMatrix,
		};

	public:
		TRotation(EEulerAnglesOrder order = kXYZOrder, EMaster master = kMatrix);
		explicit TRotation(const TMatrix33& matrix);

		EMaster				GetMaster() const { return (EMaster)fMaster; }
		void				SetEulerOrder(EEulerAnglesOrder order) { fEulerOrder = order; }
		EEulerAnglesOrder	GetEulerOrder() const { return (EEulerAnglesOrder)fEulerOrder; }
		void				ConvertEulerOrder(EEulerAnglesOrder order);

		void				SetMatrix(const TMatrix33& matrix);
		void				SetMatrix(const TVector3& i, const TVector3& j, const TVector3& k);
		void				SetEuler(const TVector3& euler, boolean mirror);

		const TMatrix33&	GetMatrix() const;
		const TVector3&		GetEuler() const;
		boolean				GetMirror() const;

		void				SetToIdentity();
		TRotation			GetTranspose() const;
		boolean				IsIdentity() const;
		boolean				IsDirect() const;
		boolean				operator == (const TRotation& rotation) const;
		boolean				operator != (const TRotation& rotation) const { return !((*this) == rotation); }

		void				SetPhyThetaPsy(real phy, real theta, real psy, boolean isDirect);
		void				GetPhyThetaPsy(real& phy, real& theta, real& psy, boolean& isDirect) const;

		void				Multiply(const TMatrix33& matrix, const TRotation& rotation);
		void				Multiply(const TRotation& rotation, const TMatrix33& matrix);
		void				Multiply(const TRotation& rotation1, const TRotation& rotation2);

		void				RecomputeEulerAngles(const TVector3& refEuler);
		void				GetNewEulerAngles	(const TVector3& refEuler, TVector3& newEulerAngles) const;

	protected:
		void				ValidateMatrix() const;
		void				ValidateEuler() const;

	protected:
		int8				fEulerOrder;
		int8				fMaster;

		mutable TMatrix33	fMatrix;
		mutable TVector3	fEuler;
		mutable boolean		fIsMatrixValid:1;
		mutable boolean		fIsEulerValid:1;
		mutable boolean		fMirror:1;
	};

public:
						TTreeTransform();
						TTreeTransform(const TTreeTransform &aT);

	boolean				IsIdentity() const;
	boolean				IsDirect() const;

	void				Reset();
	void				Expand(real scale);

	// Operators
	boolean				operator==	(const TTreeTransform& tt) const;    
	boolean				operator!=	(const TTreeTransform& tt) const	{ return !(*this == tt); }
	void				operator= 	(const TTreeTransform& tt);
	TVector3			operator*	(const TVector3& point) const;

	//Arnaud: Only because some code was using the operator*
	//you should not use it as the composition is incorrect.
	TTreeTransform		BAD_Multiply(const TTreeTransform& b) const;

	// Accessors	
	void				SetMode			(ETreeTransformMode mode)		{ fMode = mode; }
	void				SetOffset		(const TVector3& offset)		{fOffset			= offset;	}
	void				SetRotation		(const TRotation& rotation)		{ fRotation = rotation; }
	void				SetRotation		(const TMatrix33& rotation);
	void				SetRotation		(const TVector3& i, const TVector3& j, const TVector3& k);
	void				SetUniformScaling(const real& scaling)			{fUniformScaling    = scaling;	}
	void				SetHotPoint		(const TVector3& hotPoint)		{fHotPoint			= hotPoint;	}
	void				SetXYZScaling	(const TVector3& XYZScaling)	{fXYZScaling		= XYZScaling;}
	void				SetOrientation	(const TMatrix33& orientation)  {fOrientation		= orientation;}
	void				SetPhyThetaPsy	(real 		phy, 
										 real 		theta, 
										 real 		psy, 
										 boolean 	isDirect) { fRotation.SetPhyThetaPsy(phy, theta, psy, isDirect); }

	ETreeTransformMode	GetMode				() const { return fMode; }
	const TVector3& 	GetOffset			() const {return fOffset;			}
	const TMatrix33& 	GetRotation			() const {return fRotation.GetMatrix();	}
	const TRotation& 	GetRotation2 		() const {return fRotation;	}
	const real&			GetUniformScaling	() const {return fUniformScaling;	}
	const TVector3& 	GetHotPoint			() const {return fHotPoint;			}
	const TVector3& 	GetXYZScaling		() const {return fXYZScaling;		}
	const TMatrix33&	GetOrientation		() const {return fOrientation;		}
	TVector3& 			GetOffset			() { return fOffset;			}
	real&				GetUniformScaling	() { return fUniformScaling;	}
	TVector3& 			GetHotPoint			() { return fHotPoint;			}
	TVector3&			GetXYZScaling		() { return fXYZScaling;		}
	TRotation& 			GetRotation2 		() { return fRotation;			}
	TMatrix33&			GetOrientation		() { return fOrientation;		}
	inline void			GetPhyThetaPsy		(real& 		phy, 
											 real& 		theta, 
											 real& 		psy, 
											 boolean& 	isDirect) const	{ fRotation.GetPhyThetaPsy(phy, theta, psy, isDirect); }

	void				GetTransform3D(TTransform3D &aT) const;
	void				GetInverseTransform3D(TTransform3D &aT) const;
	void				GetTransform3D(TMatrix33& RR, TVector3& TT) const;

	// get the matrices to apply to a vector to go from the local space to the rotation space,
	// ie. if M is the transform matrix, R the rotation matrix, then M = TL * R * TR
	void				GetRotationSpaceRight(TMatrix33& TR, boolean inverse) const;
	void				GetRotationSpaceLeft(TMatrix33& TL, boolean inverse) const;

	// get the matrices to apply to a vector to go from the local space to the scaling space,
	// ie. if M is the transform matrix, S the scaling matrix, then M = TL * S * TR
	void				GetScalingSpaceRight(TMatrix33& TR, boolean inverse) const;
	void				GetScalingSpaceLeft(TMatrix33& TL, boolean inverse) const;

	void				GetCombinedTranslation(TVector3 &TT) const;
	TVector3			GetHotPointInParent() const;
	void				GetPlane(TPlane& aplane) const;

	///Sets this transform to match the given TTransform3D
	void				SetFromTransform3D(const TTransform3D& AA);
	void				SetFromInterpolate(const real lambda, const TTreeTransform q1, const TTreeTransform q2);

	// Translation
	void				Translate		(const TVector3& vector);
	
	// ****************************************************************************
	//								TranslateHP
	// 
	/// Translates the hot point
	/// 
	/// \param vector  the translation vector in PARENT coordinates
	/// 
	void				TranslateHP		(const TVector3& vector);


	// ****************************************************************************
	//								TranslateHPL
	// 
	/// Translates the hot point
	/// 
	/// \param vector  the translation vector in LOCAL coordinates
	/// 
	void				TranslateHPL	(const TVector3& vector);

	// Rotation

	void				RotateAxis(const TVector3& center,
								   const real sin, 
								   const real cos, 
								   const TVector3& AXIS, 
								   const real& constrainAngle = kRealZero);
	void				RotateAxis(const TVector3& center,
								   const real angle,
								   const TVector3& AXIS);
	void				Rotate(const TVector3& center,
							   const TVector3& PP, 
							   const TVector3& QQ, 
							   const TVector3& AXIS, 
							   const real& constrainAngle = kRealZero);
	void				Rotate(const TVector3& center, const TMatrix33& r);
	void				RotateL(const TMatrix33& a);
	void				RotateR(const TMatrix33& b);

	TVector3&			Transform				(const TVector3& point, TVector3& result) const;
	TVector3&			InverseTransform		(const TVector3& point, TVector3& result) const;

	TVector3&			TransformV				(const TVector3& point, TVector3& result) const;
	TVector3&			InverseTransformV		(const TVector3& point, TVector3& result) const;

	TBBox3D				TransformBBox			(const TBBox3D& box) const;
	TBBox3D				InverseTransformBBox	(const TBBox3D& box) const;

	TVector3			ScaleV					(const TVector3&) const;	// scaling only

	void				RecomputeEulerAngles	(const TVector3& refEuler);
	void				GetNewEulerAngles		(const TVector3& refEuler, TVector3& newEulerAngles) const;

	boolean				ComputeOffsetTransform	(const TTreeTransform& transform1, const TTreeTransform& transform2);
	boolean				ApplyOffsetTransform	(const TTreeTransform& offset);

	/// Convert the transform mode keeping the cooresponding TTransform3D unchanged
	void				ConvertMode				(ETreeTransformMode mode);

protected:
	ETreeTransformMode	fMode;			///< Transform mode \see ETreeTransformMode
	TVector3			fHotPoint;		///< Vector from local 0,0,0 to HotPoint
	TMatrix33			fOrientation;	///< Orientation matrix
	TRotation			fRotation;		///< Rotation matrix around HotPoint
	TVector3			fOffset;		///< Vector from parent 0,0,0 to HotPoint
	real				fUniformScaling;///< Uniform scaling
	TVector3			fXYZScaling;	///< XYZ (non-uniform) scaling around hot point
};

/// \}

/// \addtogroup Data
/// \{

///Some trees do not propagate their scaling (uniform and/or xyz) on their children, 
///but instead move them further depending on the scaling (eg growing an arm without making the fingers longer)
///To do this, we need to keep the scaling center and the scaling with the transform.
class TFatherTransform : public TMCObject1
{
public:
	TFatherTransform();

	void				Reset				();
	boolean				IsEqual				(const TFatherTransform& transform, const real& zeroTolerance) const;

	void				Multiply			(const TTreeTransform& child, const TVector3& offsetScaling, TFatherTransform& outTransform) const;
	void				Multiply			(const TTreeTransform& child, TTransform3D& outTransform) const;
	void				InverseMultiply		(const TFatherTransform& inverseFather, const TTreeTransform& child, TTransform3D& outTransform) const;

	const TTransform3D&	GetRawTransform3D		() const 		{ return fTransform; }
	const TTransform3D&	GetOffsetTransform3D	() const 		{ return fOffsetTransform; }

	void				SetRawTransform3D		(const TTransform3D& inT3D) { fTransform= inT3D; }
	void				SetOffsetTransform3D	(const TTransform3D& inT3D) { fOffsetTransform= inT3D; }

	// returns the offset to be applied to the local transform translation before multipling
	// it to the father raw TTransform3D. This is only valid for the given child transform.
	void				GetLocalTranslationOffset(const TTreeTransform& childTransform, TVector3& outOffset) const;
	
	// solve the offset of the the child transform. A child transform with an offset equal to outOffset will
	// have its modified offset (after appling the LocalTranslationOffset) equal to offsetInParent
	void				SolveOffset(const TFatherTransform& inverseFather, const TTreeTransform& childTransform, const TVector3& offsetInParent, TVector3& outOffset) const;

	// returns the TTransform3D to composite with the raw child transform (without adapting
	// the child transform offset). Note that this transform is only valid for the child
	// transform given as argument (ie. if you cannot modify the child transform offset)
	void				GetTransform3DForRawCompositing(const TTreeTransform& childTransform, TTransform3D& outTransform) const;

	void				GetInverse			(TFatherTransform& outInverse) const;

protected:
	TTransform3D	fTransform;			///the transform to composite with the adapted child's transform
	TTransform3D	fOffsetTransform;	///the transform used for the child offset (in local)
};


/// \}

/// \addtogroup Data
/// \{

class TGlobalTreeTransform : public TMCObject1
{
public:
	TGlobalTreeTransform();

	void					SetLocalTreeTransform(const TTreeTransform& transform);
	void					SetParentTransform(const TFatherTransform& transform);
	void					Reset();

	///Returns the local transform offset by the scaling of the father 
	///(ie the one to combine with the father T3D to get the global transform)
	const TTreeTransform&	GetLocalTreeTransform		() const { return fLocalTransform; }
	TTreeTransform&			GetLocalTreeTransform		() { return fLocalTransform; }
	const TFatherTransform&	GetParentTransform			() const { return fParentTransform; }
	const TFatherTransform&	GetParentInverseTransform	() const { ValidateParentInverseTransform(); return fParentInverseTransform; }

	// Set/Get
	const TVector3& 		GetHotPoint			() const { return fLocalTransform.GetHotPoint(); }
	TVector3				GetHotPointInGlobal	() const;
	TVector3				GetOffset			() const { return fParentTransform.GetRawTransform3D().TransformPoint(fLocalTransform.GetOffset() + fLocalOffset); }
	TMatrix33				GetRotation			() const;	// WARNING: the resulting matrix can be not orthonormalized

	void			 		SetHotPoint			(const TVector3& hotPoint) { fLocalTransform.SetHotPoint(hotPoint); }
	void			 		SetHotPointInGlobal	(const TVector3& globalHotPoint);
	void					SetOffset			(const TVector3& offset);
	void					SetOffsetFromGlobalHotPointPosition(const TVector3& globalHotPointPosition);
	void					SetRotation			(const TMatrix33& rotation, int32 mainAxis = 2, boolean keepEulerAngles = false);
	
	// Operations
	void					Translate(const TVector3& globalVector);
	void					TranslateHP(const TVector3& globalVector);

	// Convert functions
	void					GetTransform3D			(TTransform3D &aT) const;
	void					GetInverseTransform3D	(TTransform3D &aT) const;
	// The following function should be avoided because doesn't work
	// if the parent transform is not "simple" (no non-uniform scaling)
	void					GetTreeTransform		(TTreeTransform &aT) const;

	// transform function (those functions are not very fast, we might prefer to use
	// the TTransform3D)
	inline TVector3			TransformPoint			(const TVector3& point) const;
	inline TVector3			TransformVector			(const TVector3& vector) const;
	inline TVector3			InverseTransformPoint	(const TVector3& point) const;
	inline TVector3			InverseTransformVector	(const TVector3& vector) const;

	// Miscs
	void					GetPlane(TPlane& aplane) const;

protected:
	inline void				ValidateParentInverseTransform() const
	{
		if (!fIsParentInverseTransformValid)
		{
			fParentTransform.GetInverse(fParentInverseTransform);
			fIsParentInverseTransformValid = true;
		}
	}

protected:
	TTreeTransform				fLocalTransform;
	TVector3					fLocalOffset;			///the offset to apply in local (see TFatherTransform)
	TFatherTransform			fParentTransform;
	mutable TFatherTransform	fParentInverseTransform;
	mutable boolean				fIsParentInverseTransformValid;
};

/// \}

inline TVector3 TGlobalTreeTransform::TransformPoint(const TVector3& point) const
{
	TVector3 out;
	fLocalTransform.Transform(point, out);
	out += fLocalOffset;
	out = fParentTransform.GetRawTransform3D().TransformPoint(out);
	return out;
}

inline TVector3 TGlobalTreeTransform::TransformVector(const TVector3& vector) const
{
	TVector3 out;
	fLocalTransform.TransformV(vector, out);
	out = fParentTransform.GetRawTransform3D().TransformVector(out);
	return out;
}

inline TVector3 TGlobalTreeTransform::InverseTransformPoint(const TVector3& point) const
{
	ValidateParentInverseTransform();

	TVector3 out;
	out = fParentInverseTransform.GetRawTransform3D().TransformPoint(point);
	out -= fLocalOffset;
	fLocalTransform.InverseTransform(out, out);
	return out;
}

inline TVector3 TGlobalTreeTransform::InverseTransformVector(const TVector3& vector) const
{
	ValidateParentInverseTransform();

	TVector3 out;
	out = fParentInverseTransform.GetRawTransform3D().TransformVector(vector);
	fLocalTransform.InverseTransformV(out, out);
	return out;
}


#endif

