/** 
 * @file xform.h
 *
 * $LicenseInfo:firstyear=2001&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#ifndef LL_XFORM_H
#define LL_XFORM_H

#include "v3math.h"
#include "m4math.h"
#include "llquaternion.h"

constexpr F32 SL_MAX_OBJECT_Z 	= 4096.f; // should match REGION_HEIGHT_METERS, Pre-havok4: 768.f
constexpr F32 OS_MAX_OBJECT_Z	= 10000.f;
constexpr F32 MIN_OBJECT_Z 		= -256.f;
constexpr F32 SL_DEFAULT_MAX_PRIM_SCALE = 64.f;
constexpr F32 OS_DEFAULT_MAX_PRIM_SCALE = 256.f;
constexpr F32 SL_DEFAULT_MAX_PRIM_SCALE_NO_MESH = 10.f;
constexpr F32 SL_MIN_PRIM_SCALE = 0.01f;
constexpr F32 OS_MIN_PRIM_SCALE = 0.001f;
constexpr F32 MAX_PRIM_SCALE = 65536.f;	// something very high but not near FLT_MAX


class LLXform
{
protected:
	LLVector3	  mPosition;
	LLQuaternion  mRotation; 
	LLVector3	  mScale;
	
	//RN: TODO: move these world transform members to LLXformMatrix
	// as they are *never* updated or accessed in the base class
	LLVector3	  mWorldPosition;
	LLQuaternion  mWorldRotation;

	LLXform*      mParent;
	U32			  mChanged;

	BOOL		  mScaleChildOffset;

public:
	typedef enum e_changed_flags
	{
		UNCHANGED  	= 0x00,
		TRANSLATED 	= 0x01,
		ROTATED		= 0x02,
		SCALED		= 0x04,
		SHIFTED		= 0x08,
		GEOMETRY	= 0x10,
		TEXTURE		= 0x20,
		MOVED       = TRANSLATED|ROTATED|SCALED,
		SILHOUETTE	= 0x40,
		ALL_CHANGED = 0x7f
	}EChangedFlags;

	void init()
	{
		mParent  = nullptr;
		mChanged = UNCHANGED;
		mPosition.setVec(0,0,0);
		mRotation.loadIdentity();
		mScale.   setVec(1,1,1);
		mWorldPosition.clearVec();
		mWorldRotation.loadIdentity();
		mScaleChildOffset = FALSE;
	}

	LLXform();
	virtual ~LLXform() = default;

	void getLocalMat4(LLMatrix4 &mat) const { mat.initAll(mScale, mRotation, mPosition); }

	inline BOOL setParent(LLXform *parent);

	inline void setPosition(const LLVector3& pos);
	inline void setPosition(const F32 x, const F32 y, const F32 z);
	inline void setPositionX(const F32 x);
	inline void setPositionY(const F32 y);
	inline void setPositionZ(const F32 z);
	inline void addPosition(const LLVector3& pos);


	inline void setScale(const LLVector3& scale);
	inline void setScale(const F32 x, const F32 y, const F32 z);
	inline void setRotation(const LLQuaternion& rot);
	inline void setRotation(const F32 x, const F32 y, const F32 z);
	inline void setRotation(const F32 x, const F32 y, const F32 z, const F32 s);

	// Above functions must be inline for speed, but also
	// need to emit warnings.  LL_WARNS() causes inline LLError::CallSite
	// static objects that make more work for the linker.
	// Avoid inline LL_WARNS() by calling this function.
	void warn(const char* const msg);
	
	void 		setChanged(const U32 bits)					{ mChanged |= bits; }
	BOOL		isChanged() const							{ return mChanged; }
	BOOL 		isChanged(const U32 bits) const				{ return mChanged & bits; }
	void 		clearChanged()								{ mChanged = 0; }
	void        clearChanged(U32 bits)                      { mChanged &= ~bits; }

	void		setScaleChildOffset(BOOL scale)				{ mScaleChildOffset = scale; }
	BOOL		getScaleChildOffset()						{ return mScaleChildOffset; }

	LLXform* getParent() const { return mParent; }
	LLXform* getRoot() const;
	virtual BOOL isRoot() const;
	virtual BOOL isRootEdit() const;

	const LLVector3&	getPosition()  const	    { return mPosition; }
	const LLVector3&	getScale() const			{ return mScale; }
	const LLQuaternion& getRotation() const			{ return mRotation; }
	const LLVector3&	getPositionW() const		{ return mWorldPosition; }
	const LLQuaternion& getWorldRotation() const	{ return mWorldRotation; }
	const LLVector3&	getWorldPosition() const	{ return mWorldPosition; }
};

class LLXformMatrix : public LLXform
{
public:
	LLXformMatrix() = default;
	virtual ~LLXformMatrix() = default;

	const LLMatrix4&    getWorldMatrix() const      { return mWorldMatrix; }
	void setWorldMatrix (const LLMatrix4& mat)   { mWorldMatrix = mat; }

	void init()
	{
		mWorldMatrix.setIdentity();
		mMin.clearVec();
		mMax.clearVec();

		LLXform::init();
	}

	void update();
	void updateMatrix(BOOL update_bounds = TRUE);
	void getMinMax(LLVector3& min,LLVector3& max) const;

protected:
	LLMatrix4	mWorldMatrix;
	LLVector3	mMin;
	LLVector3	mMax;

};

BOOL LLXform::setParent(LLXform* parent)
{
	// Validate and make sure we're not creating a loop
	if (parent == mParent)
	{
		return TRUE;
	}
	if (parent)
	{
		LLXform *cur_par = parent->mParent;
		while (cur_par)
		{
			if (cur_par == this)
			{
				//warn("LLXform::setParent Creating loop when setting parent!");
				return FALSE;
			}
			cur_par = cur_par->mParent;
		}
	}
	mParent = parent;
	return TRUE;
}

void LLXform::setPosition(const LLVector3& pos)			
{
	setChanged(TRANSLATED);
	if (pos.isFinite())
		mPosition = pos; 
	else
	{
		mPosition.clearVec();
		warn("Non Finite in LLXform::setPosition(LLVector3)");
	}
}

void LLXform::setPosition(const F32 x, const F32 y, const F32 z)
{
	setChanged(TRANSLATED);
	if (llfinite(x) && llfinite(y) && llfinite(z))
		mPosition.setVec(x,y,z); 
	else
	{
		mPosition.clearVec();
		warn("Non Finite in LLXform::setPosition(F32,F32,F32)");
	}
}

void LLXform::setPositionX(const F32 x)
{ 
	setChanged(TRANSLATED);
	if (llfinite(x))
		mPosition.mV[VX] = x; 
	else
	{
		mPosition.mV[VX] = 0.f;
		warn("Non Finite in LLXform::setPositionX");
	}
}

void LLXform::setPositionY(const F32 y)
{ 
	setChanged(TRANSLATED);
	if (llfinite(y))
		mPosition.mV[VY] = y; 
	else
	{
		mPosition.mV[VY] = 0.f;
		warn("Non Finite in LLXform::setPositionY");
	}
}

void LLXform::setPositionZ(const F32 z)
{ 
	setChanged(TRANSLATED);
	if (llfinite(z))
		mPosition.mV[VZ] = z; 
	else
	{
		mPosition.mV[VZ] = 0.f;
		warn("Non Finite in LLXform::setPositionZ");
	}
}

void LLXform::addPosition(const LLVector3& pos)
{ 
	setChanged(TRANSLATED);
	if (pos.isFinite())
		mPosition += pos; 
	else
		warn("Non Finite in LLXform::addPosition");
}

void LLXform::setScale(const LLVector3& scale)
{ 
	setChanged(SCALED);
	if (scale.isFinite())
		mScale = scale; 
	else
	{
		mScale.setVec(1.f, 1.f, 1.f);
		warn("Non Finite in LLXform::setScale");
	}
}
void LLXform::setScale(const F32 x, const F32 y, const F32 z)
{ 
	setChanged(SCALED);
	if (llfinite(x) && llfinite(y) && llfinite(z))
		mScale.setVec(x,y,z); 
	else
	{
		mScale.setVec(1.f, 1.f, 1.f);
		warn("Non Finite in LLXform::setScale");
	}
}
void LLXform::setRotation(const LLQuaternion& rot)
{ 
	setChanged(ROTATED);
	if (rot.isFinite())
		mRotation = rot; 
	else
	{
		mRotation.loadIdentity();
		warn("Non Finite in LLXform::setRotation");
	}
}
void LLXform::setRotation(const F32 x, const F32 y, const F32 z) 
{ 
	setChanged(ROTATED);
	if (llfinite(x) && llfinite(y) && llfinite(z))
	{
		mRotation.setQuat(x,y,z); 
	}
	else
	{
		mRotation.loadIdentity();
		warn("Non Finite in LLXform::setRotation");
	}
}
void LLXform::setRotation(const F32 x, const F32 y, const F32 z, const F32 s) 
{ 
	setChanged(ROTATED);
	if (llfinite(x) && llfinite(y) && llfinite(z) && llfinite(s))
	{
		mRotation.mQ[VX] = x; mRotation.mQ[VY] = y; mRotation.mQ[VZ] = z; mRotation.mQ[VS] = s; 
	}
	else
	{
		mRotation.loadIdentity();
		warn("Non Finite in LLXform::setRotation");
	}
}

#endif
