/*
 * $Id: camera.h 4662 2017-08-24 09:46:00Z antonin.misek-skoda-auto.cz $
 *
 * Description  : One line description of file.
 * Author       : Vaclav Kyba <mail/Jabber: vaseo1@gmail.com>  <ICQ: 98576293>
 *
 * Purpose      :
 *     Scene parent influences only position of camera.
 */


#ifndef __CAMERA__H__
#define __CAMERA__H__

//#include "common.h"
#include "bvolumes.h"
#include "scenenode.h"
#include "smartptr.h"
#include "VrutApi.h"
//#include "scene.h"


namespace VRUT
{
	class Scene;

	///Camera scene graph node
	class VRUT_CORE_API Camera : public SceneNode
	{
	public:
		static const SCENENODE_TYPE ClassType = CAMERA;

		///Intersection result
		enum VFC_RESULT
		{
			OUTSIDE = 0,    ///<Object is outside VF
			INSIDE,         ///<Object is inside VF
			INTERSECT       ///<Object is partially inside and partially outside
		};

		///Frustum plane enumerator
		enum FRUSTUM_PLANE
		{
			FRUSTUM_NEAR = 0,  ///<Near plane index
			FRUSTUM_FAR,       ///<Far plane index
			FRUSTUM_LEFT,      ///<Left plane index
			FRUSTUM_RIGHT,     ///<Right plane index
			FRUSTUM_TOP,       ///<Top plane index
			FRUSTUM_BOTTOM     ///<Bottom plane index
		};

		///Projection type enumerator
		enum PROJECTION_TYPE{
			PERSPECTIVE_CENTERED = 0,	///<Common FOV-based perspective
			PERSPECTIVE_GENERIC,	///<Off-center perspective
		};

		///Orientation of offset for a stereoscopic camera
		enum EYE_TYPE {
			EYE_CENTER = 0,			///<Center (no offset)
			EYE_LEFT = 1,			///<Left eye (negative X offset)
			EYE_RIGHT = 2,			///<Right eye (positive X offset)
			EYE_ACTIVE_STEREO = 3	///<Both eyes (negativ and positiv X offset)
		};

		enum UP_FIX {
			UP_FREE = 0,	///allow arbitrary transformation
			UP_FIX_Z,		///fix up direction to axis Z
		};

		///Set model space transformation matrix
		virtual void SetLocalTransMatrix(const Matrix &matrix)
		{
			if (upFix==UP_FIX_Z)
			{
				float x, y, z;
				matrix.ToEuler(x, y, z);
				Matrix transform(Matrix::RotationX(-x)*Matrix::RotationZ(-z)*Matrix::Translation(matrix.ExtractTranslation()));
				localTransMatrix=transform;
			}
			else
				localTransMatrix=matrix;
		}
        private:
		///Projection matrix
		Matrix projMat;
		// T * MT matrix
		Matrix TMTMatrix;
		///Bounding sphere
		BSphere bSphere;
		///Near plane distance
		float nearPlane;
		///Far plane distance
		float farPlane;
		///Field of view (angles)
		float fov;
		///Auxiliary - last viewport width
		unsigned int lastWidth;
		///Auxiliary - last viewport width
		unsigned int lastHeight;
		///Center point (look at point) distance from eye position
		float centerDist;
		///Current projection type
		PROJECTION_TYPE projectionType;
		///Viewpoint offset
		Vector3 offset;
		///The "eyedness" of this camera
		EYE_TYPE eye;
		UP_FIX upFix;
		///only for stereo camera
		float eyeSeparation;
		//definition of generic projection
		Vector3 pa;//left bottom
		Vector3 pb;//right bottom
		Vector3 pc;//left up
		Vector3 eyeAxis;//eye axis, so pe=offset+eyeAxis*eyeSeparation
		Vector3 lookAt; //Look direction, comes from tracking, otherwise (0, 0, -1) in view space

		///Update projection matrix (don't call directly, use \c Scene::UpdateTransformation instead)
		///activeStereoEye - false=generate image for left eye, true=generate image for right eye
		void updateProjection(float aspectRatio, bool activeStereoEye)
		{
			//LOG(wxString::Format(wxT("<Camera::updateProjection>camID=%lu, near=%f, far=%f, FOV=%f, lastWidth=%i, lastHeight=%i, projType=%i, centerDist=%f, eye=%i, eyeSeparation=%f, upFix=%i, eyeAxis="),
			//	(unsigned long)GetID(), nearPlane, farPlane, fov, lastWidth, lastHeight, projectionType, centerDist, eye, eyeSeparation, upFix)+eyeAxis.ToString());

			switch (projectionType) {
			case PERSPECTIVE_CENTERED:
				projMat = Matrix::PerspectiveCentered(fov, aspectRatio, nearPlane, farPlane);
				break;
			case PERSPECTIVE_GENERIC:
			{
				//http://csc.lsu.edu/~kooima/articles/genperspective/index.html
				Vector3 vr((pb - pa).Normalize());
				Vector3 vu((pc - pa).Normalize());
				Vector3 vn(vr.Cross(vu).Normalize());
				Vector3 pe = offset;
				if ((eye == EYE_LEFT) || ((eye == EYE_ACTIVE_STEREO) && !activeStereoEye))
					pe -= eyeAxis*eyeSeparation / 2.0f;
				else if ((eye == EYE_RIGHT) || ((eye == EYE_ACTIVE_STEREO) && activeStereoEye))
					pe += eyeAxis*eyeSeparation / 2.0f;
				Vector3 va(pa - pe);
				Vector3 vb(pb - pe);
				Vector3 vc(pc - pe);
				float d = -vn.Dot(va);
				float l = vr.Dot(va)*nearPlane / d;
				float r = vr.Dot(vb)*nearPlane / d;
				float b = vu.Dot(va)*nearPlane / d;
				float t = vu.Dot(vc)*nearPlane / d;
				Matrix P = Matrix::PerspectiveGeneric(l, r, b, t, nearPlane, farPlane);
				Matrix MT;
				MT.SetColumn(0, vr);
				MT.SetColumn(1, vu);
				MT.SetColumn(2, vn);
				Matrix T(Matrix::Translation(-pe));

				//projMat = T*MT*P;
				TMTMatrix = T*MT;
				projMat = TMTMatrix*P;
			}
			break;
			}
		}

	public:

		///Class constructor
		Camera(const wxString & _name, EYE_TYPE type = EYE_CENTER) : SceneNode(_name, ClassType), nearPlane(1.0f), farPlane(10000.0f), fov(45.0f), lastWidth(640), lastHeight(480), centerDist(30.0f),
			projectionType(PERSPECTIVE_CENTERED), eye(type), upFix(UP_FREE), eyeSeparation(0.0f), eyeAxis(0, 1, 0), lookAt(0, 0, -1) {}

		Camera(const wxString & _name, smart_pointer<Camera> & c) : SceneNode(_name, SceneNode::CAMERA), projMat(c->projMat), bSphere(c->bSphere), nearPlane(c->nearPlane), farPlane(c->farPlane), fov(c->fov),
			lastWidth(c->lastWidth), lastHeight(c->lastHeight), centerDist(c->centerDist), projectionType(c->projectionType), offset(c->offset), eye(c->eye), upFix(c->upFix), eyeSeparation(c->eyeSeparation),
			pa(c->pa), pb(c->pb), pc(c->pc), eyeAxis(c->eyeAxis), lookAt(c->lookAt) {}

		///Copy constructor
		Camera(const Camera & c) : SceneNode(c), projMat(c.projMat), bSphere(c.bSphere), nearPlane(c.nearPlane), farPlane(c.farPlane), fov(c.fov), lastWidth(c.lastWidth), lastHeight(c.lastHeight),
			centerDist(c.centerDist), projectionType(c.projectionType), offset(c.offset), eye(c.eye), upFix(c.upFix), eyeSeparation(c.eyeSeparation), pa(c.pa), pb(c.pb), pc(c.pc), eyeAxis(c.eyeAxis), lookAt(c.lookAt)
		{
		}
		///Class destructor
		virtual ~Camera() {}

		///Get center distance from eye position
		float GetCenterDist() const
		{
			return centerDist;
		}

		///Set center distance from eye position
		void SetCenterDist(float dist)
		{
			centerDist = dist;
		}

		///Set eye separation
		void SetEyeSeparation(float _eyeSeparation)
		{
			eyeSeparation = _eyeSeparation;
		}

		///Get FOV
		float GetFOV() const
		{
			return fov;
		}

		///Get farPlane
		float GetFarPlane() const
		{
			return farPlane;
		}

		///Get nearPlane
		float GetNearPlane() const
		{
			return nearPlane;
		}

		///Get lastWidth
		unsigned int GetLastWidth() const
		{
			return lastWidth;
		}

		///Get lastHeight
		unsigned int GetLastHeight() const
		{
			return lastHeight;
		}

		///Get projectionType
		PROJECTION_TYPE GetProjectionType() const
		{
			return projectionType;
		}

		///Get offset
		///Returns the offset vector - that is, the actual position of the observer in normalized (camera) coordinates,
		///which correspond to tangential coordinates of the viewing surface, with bitangent being the surface's up vector.
		///Only makes sense in generic perspective, otherwise the observer position is (0,0,0) after the ModelView transformation.
		///This is tested explicitly, just to be sure.
		Vector3 GetOffset() const
		{
			return offset;
		}

		///Get eye
		EYE_TYPE GetEye() const
		{
			return eye;
		}

		void GetPabc(Vector3 &pa, Vector3 &pb, Vector3 &pc) const
		{
			pa=this->pa;
			pb=this->pb;
			pc=this->pc;
		}

		Vector3 GetEyeAxis() const
		{
			return eyeAxis;
		}

		Vector3 GetLookAt() const
		{
			return lookAt;
		}

		///Get bounding sphere
		const BSphere * GetBSphere() const
		{
			return &bSphere;
		}

		unsigned GetWidth() const {
			return lastWidth;
		}

		unsigned GetHeight() const {
			return lastHeight;
		}

		///Get eye separation
		float GetEyeSeparation() const {
			return eyeSeparation;
		}

		// Get T*MT matrix
		const Matrix * GetTMTMatrix() const
		{
			return &TMTMatrix;
		}


		///Get up fix mode
		UP_FIX GetUpFix() const
		{
			return upFix;
		}

		///Set up fix mode
		void SetUpFix(UP_FIX _upFix)
		{
			if (upFix!=_upFix)
			{
				upFix=_upFix;
				SetLocalTransMatrix(*GetLocalTransMatrix());
			}
		}

		void SetProjectionType(PROJECTION_TYPE type)
		{
			projectionType = type;
		}

		void SetType(EYE_TYPE type)
		{
			eye = type;
		}

		///Get string with node's properties - \c SceneNode overload
		virtual wxString ToString() const
		{
			wxString ret(SceneNode::ToString());
			ret+=wxT("Projection: \n")+projMat.ToString();
			ret+=wxT("\nBound sphere:\n")+bSphere.ToString();
			ret+=wxT("\nNear plane: ")+TextWriter::WriteToWxString(nearPlane)+wxT("\nFar plane: ")+TextWriter::WriteToWxString(farPlane)+wxT("\nFOV: ")+TextWriter::WriteToWxString(fov)
				+wxT("\nCenter distance: ")+TextWriter::WriteToWxString(centerDist)+wxString::Format(wxT("\nUp fix: %i\n"), upFix);
			ret+=(projectionType==PERSPECTIVE_CENTERED?(wxT("Centered perspective\n")):(wxT("Generic perspective\n")));
			switch(eye) {
			case EYE_CENTER:
				ret+=wxT("Eye: center\n");
				break;
			case EYE_LEFT:
				ret+=wxT("Eye: left\n");
				break;
			case EYE_RIGHT:
				ret+=wxT("Eye: right\n");
				break;
			case EYE_ACTIVE_STEREO:
				ret+=wxT("Eye: stereo\n");
				break;
			}
			ret+=wxT("Offset: ")+offset.ToString();
			ret+=wxT("\nEyeSeparation: ")+TextWriter::WriteToWxString(eyeSeparation);
			ret+=wxString::Format(wxT("\nLastWidth: %i, LastHeight: %i\n"), lastWidth, lastHeight);
			ret+=wxT("pa: ")+pa.ToString()+wxT(", pb: ")+pb.ToString()+wxT(", pc: ")+pc.ToString()+wxT(", eyeAxis: ")+eyeAxis.ToString();
			return ret;
		}

		///Get copy of instance - \c SceneNode overload
		virtual SceneNode * Clone() const
		{
			return new Camera(*this);
		}

		friend class Scene;
	};
}

#endif
