//////////////////////////////////////////////////////////////////////////////////////////////////
// 
// File: camera.cpp
// 
// Author: Frank Luna (C) All Rights Reserved
// Edited by: Chris Kaynor
//
// System: AMD Athlon 1800+ XP, 512 DDR, Geforce 3, Windows XP, MSVC++ 7.0 
//
// Desc: Defines a camera's position and orientation.
//         
//////////////////////////////////////////////////////////////////////////////////////////////////

#include "camera.h"

using namespace cbk;

namespace CBK
{
	Camera::Camera(CameraType cameraType, IDirect3DDevice9* device) : Object(device)
	{
		_cameraType = cameraType;

		_pos   = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
		_right = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
		_up    = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
		_look  = D3DXVECTOR3(0.0f, 0.0f, 1.0f);
		_rotation = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
		_terrainOffset = 300.0f;

		viewFrust = 0;
		viewMatrix = 0;
	}

	Camera::~Camera()
	{
		if (viewFrust != 0)
			delete viewFrust;
		if (viewMatrix != 0)
			delete viewMatrix;
	}

	Object& Camera::reset(void)
	{
		return *this;
	}

	const float& Camera::getTerrainOffset(void) const
	{
		return _terrainOffset;
	}

	Camera& Camera::setTerrainOffset(const float& offset)
	{
		_terrainOffset = offset;
		return *this;
	}

	Camera& Camera::setRotation(const D3DXVECTOR3& pos)
	{
		_right = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
		_up    = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
		_look  = D3DXVECTOR3(0.0f, 0.0f, 1.0f);

		_rotation = D3DXVECTOR3(0.0f, 0.0f, 0.0f);

		yaw(pos.x);
		pitch(pos.y);
		roll(pos.z);

		if (viewFrust != 0)
			delete viewFrust;
		if (viewMatrix != 0)
			delete viewMatrix;
		viewFrust = 0;
		viewMatrix = 0;

		return *this;
	}

	Camera& Camera::setRotation(const float& yaw, const float& pitch, const float& roll)
	{
		_right = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
		_up    = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
		_look  = D3DXVECTOR3(0.0f, 0.0f, 1.0f);

		_rotation = D3DXVECTOR3(0.0f, 0.0f, 0.0f);

		this->yaw(yaw);
		this->pitch(pitch);
		this->roll(roll);

		if (viewFrust != 0)
			delete viewFrust;
		if (viewMatrix != 0)
			delete viewMatrix;
		viewFrust = 0;
		viewMatrix = 0;

		return *this;
	}

	const D3DXVECTOR3& Camera::getPosition(void) const
	{
		return _pos;
	}

	Camera& Camera::setPosition(const D3DXVECTOR3& pos)
	{
		_pos = pos;

		if (viewFrust != 0)
			delete viewFrust;
		if (viewMatrix != 0)
			delete viewMatrix;
		viewFrust = 0;
		viewMatrix = 0;

		return *this;
	}

	Camera& Camera::setPosition(const float& x, const float& y, const float& z)
	{
		_pos.x = x;
		_pos.y = y;
		_pos.z = z;

		if (viewFrust != 0)
			delete viewFrust;
		if (viewMatrix != 0)
			delete viewMatrix;
		viewFrust = 0;
		viewMatrix = 0;

		return *this;
	}

	const D3DXVECTOR3& Camera::getRight(void) const
	{
		return _right;
	}

	const D3DXVECTOR3& Camera::getUp(void) const
	{
		return _up;
	}

	const D3DXVECTOR3& Camera::getLook(void) const
	{
		return _look;
	}

	Camera& Camera::walk(const float& units)
	{
		// move only on xz plane for land object
		if( _cameraType == LANDOBJECT )
			_pos += D3DXVECTOR3(_look.x, 0.0f, _look.z) * units;

		if( _cameraType == AIRCRAFT )
			_pos += _look * units;

		if (viewFrust != 0)
			delete viewFrust;
		if (viewMatrix != 0)
			delete viewMatrix;
		viewFrust = 0;
		viewMatrix = 0;

		return *this;
	}

	Camera& Camera::strafe(const float& units)
	{
		// move only on xz plane for land object
		if( _cameraType == LANDOBJECT )
			_pos += D3DXVECTOR3(_right.x, 0.0f, _right.z) * units;

		if( _cameraType == AIRCRAFT )
			_pos += _right * units;

		if (viewFrust != 0)
			delete viewFrust;
		if (viewMatrix != 0)
			delete viewMatrix;
		viewFrust = 0;
		viewMatrix = 0;

		return *this;
	}

	Camera& Camera::fly(const float& units)
	{
		// move only on y-axis for land object
		if( _cameraType == LANDOBJECT )
			_pos.y += units;

		if( _cameraType == AIRCRAFT )
			_pos += _up * units;

		if (viewFrust != 0)
			delete viewFrust;
		if (viewMatrix != 0)
			delete viewMatrix;
		viewFrust = 0;
		viewMatrix = 0;

		return *this;
	}

	Camera& Camera::pitch(const float& angle)
	{
		D3DXMATRIX T;
		D3DXMatrixRotationAxis(&T, &_right,	angle);

		// rotate _up and _look around _right vector
		D3DXVec3TransformCoord(&_up,&_up, &T);
		D3DXVec3TransformCoord(&_look,&_look, &T);

		_rotation.y += angle;

		if (viewFrust != 0)
			delete viewFrust;
		if (viewMatrix != 0)
			delete viewMatrix;
		viewFrust = 0;
		viewMatrix = 0;

		return *this;
	}

	Camera& Camera::yaw(const float& angle)
	{
		D3DXMATRIX T;

		// rotate around world y (0, 1, 0) always for land object
		if( _cameraType == LANDOBJECT )
			D3DXMatrixRotationY(&T, angle);

		// rotate around own up vector for aircraft
		if( _cameraType == AIRCRAFT )
			D3DXMatrixRotationAxis(&T, &_up, angle);

		// rotate _right and _look around _up or y-axis
		D3DXVec3TransformCoord(&_right,&_right, &T);
		D3DXVec3TransformCoord(&_look,&_look, &T);

		_rotation.x += angle;

		if (viewFrust != 0)
			delete viewFrust;
		if (viewMatrix != 0)
			delete viewMatrix;
		viewFrust = 0;
		viewMatrix = 0;

		return *this;
	}

	Camera& Camera::roll(const float& angle)
	{
		// only roll for aircraft type
		if( _cameraType == AIRCRAFT )
		{
			D3DXMATRIX T;
			D3DXMatrixRotationAxis(&T, &_look,	angle);

			// rotate _up and _right around _look vector
			D3DXVec3TransformCoord(&_right,&_right, &T);
			D3DXVec3TransformCoord(&_up,&_up, &T);

			_rotation.z += angle;
		}

		if (viewFrust != 0)
			delete viewFrust;
		if (viewMatrix != 0)
			delete viewMatrix;
		viewFrust = 0;
		viewMatrix = 0;

		return *this;
	}

	const Frustum& Camera::getViewFrustum(const D3DXMATRIX& P, const D3DVIEWPORT9& V)
	{
		if (viewFrust == 0)
		{
			const D3DXMATRIX& t = getViewMatrix();
			viewFrust = new Frustum(t, P, V);
		}

		return (*viewFrust);
	}

	const D3DXMATRIX& Camera::getViewMatrix(void)
	{
		if (viewMatrix == 0)
		{
			viewMatrix = new D3DXMATRIX();
			D3DXMATRIX& V = *viewMatrix;
			// Keep camera's axes orthogonal to eachother
			D3DXVec3Normalize(&_look, &_look);

			D3DXVec3Cross(&_up, &_look, &_right);
			D3DXVec3Normalize(&_up, &_up);

			D3DXVec3Cross(&_right, &_up, &_look);
			D3DXVec3Normalize(&_right, &_right);

			// Build the view matrix:
			float x = -D3DXVec3Dot(&_right, &_pos);
			float y = -D3DXVec3Dot(&_up, &_pos);
			float z = -D3DXVec3Dot(&_look, &_pos);

			(V)(0,0) = _right.x; (V)(0, 1) = _up.x; (V)(0, 2) = _look.x; (V)(0, 3) = 0.0f;
			(V)(1,0) = _right.y; (V)(1, 1) = _up.y; (V)(1, 2) = _look.y; (V)(1, 3) = 0.0f;
			(V)(2,0) = _right.z; (V)(2, 1) = _up.z; (V)(2, 2) = _look.z; (V)(2, 3) = 0.0f;
			(V)(3,0) = x;        (V)(3, 1) = y;     (V)(3, 2) = z;       (V)(3, 3) = 1.0f;
		}
		
		return *viewMatrix;
	}

	Camera& Camera::setCameraType(CameraType cameraType)
	{
		_cameraType = cameraType;
		return *this;
	}

	Object& Camera::load(const std::string& name)
	{
		name;
		return *this;
	}

	void Camera::apply(void)
	{
		device->SetTransform(D3DTS_VIEW, &getViewMatrix());
	}

	const D3DXVECTOR3& Camera::getRotation(void) const
	{
		return _rotation;
	}
}