//	Darkstar Engine  (c) UnderWorld Interactive Studio (all coding by Svarog    2007)
//
//      Данный файл является частью Darkstar Engine. Любое использование кода файла
//      без согласия компании, является нарушением закона об авторском праве (в слу-
//      чае если не оговорено обратное).
//
//      Имя файла: fpcamera_controller.cpp
//      Краткое описание: First person camera controller
//

#include <Darkstar/Graphic System/Camera/fpcamera_controller.hpp>
#include <Darkstar/Graphic System/Direct3D/graphicDriver.hpp>

using gs::Camera;
using gs::FPCameraController;
using math::Vect3;
using math::Quat;
using math::deg_to_rad;
using gs::GraphicDriver;

FPCameraController::FPCameraController(const shared_ptr<Camera>& cam, bool is_watch_on_resize)
{
	angx = angy = 0;
	camera = cam;
	Vect3 direction;
	if(cam->getTarget() - cam->getPosition() != math::vect3_zero)
	{
		direction = (cam->getTarget() - cam->getPosition()).getNormalize();
		angx = asinf(direction.y)*math::rad_to_deg;
		float dist = sqrtf(direction.x*direction.x + direction.z*direction.z);
		float sin_ang = direction.z / dist;
		float cos_ang = direction.x / dist;
		float ang;
		if(math::float_notequal(cos_ang, 0))
		{
			float tan_ang = direction.z / direction.x;
			ang = atanf(tan_ang);
			if(cos_ang < 0)
				ang += math::pi2;
		}
		else
			if(sin_ang > 0)
				ang = math::pi2;
			else
				ang = -math::pi2;
		angy = (ang - math::pi2)*math::rad_to_deg;
	}
	else
	{
		camera->setTarget(position + _calcDirection());
		camera->setUp(_calcUp());
	};
	position = cam->getPosition();
	Vect3 cdir = _calcDirection();
	Vect3 cup = _calcUp();
	if(is_watch_on_resize)
	{
		boost::function2<void, const unsigned int, const unsigned int> new_function;
		new_function = boost::bind(&gs::FPCameraController::setNewAspect, this, _1, _2);
		GraphicDriver::ptr()->addMessageListenerOnResize(new_function);
	};
};

FPCameraController::~FPCameraController()
{

};

Vect3 FPCameraController::_calcDirection() const
{	
	Vect3 res = math::vect3_sing_z;
	math::Quat rot;
	// вращение при помощи кватернионов происходит по часовой стрелке в направлении вектора
	// вокруг которого мы вращаем, но мы используем левосторонюю систему координат. Это значит
	// что положительный поворот вокруг оси Ох осуществляется от Оz к Oy. Значит, так как наш угол
	// соответствует углу в левосторонней системе отсчета, то мы должны вращать относитлено отри-
	// цательного направления Ох
	rot.setRotation(angx*deg_to_rad, -math::vect3_sing_x);
	res = rot.rotate(res);
	// минус перед вектором нужен по изложенным в прошлом комментарии причинам
	rot.setRotation(angy*deg_to_rad, -math::vect3_sing_y);
	res = rot.rotate(res);
	return res;
};

Vect3 FPCameraController::_calcUp() const
{
	Vect3 res = math::vect3_sing_y;
	math::Quat rot;
	rot.setRotation(angx*deg_to_rad, -math::vect3_sing_x);
	res = rot.rotate(res);
	rot.setRotation(angy*deg_to_rad, -math::vect3_sing_y);
	res = rot.rotate(res);
	return res;
};

void FPCameraController::move(const float amount)
{
	Vect3 path = _calcDirection();
	path.normalize();
	position += path*amount;
	camera->setPosition(position);
	camera->setTarget(position + path);
};

void FPCameraController::rotateX(const float angle)
{
	angx += angle;
	if(angx > 70)
		angx = 70;
	if(angx < -70)
		angx = -70;
	camera->setTarget(position + _calcDirection());
	camera->setUp(_calcUp());
};

void FPCameraController::rotateY(const float angle)
{
	angy += angle;
	if(angy > 180)
		angy -= 360;
	if(angy < -180)
		angy += 360;
	camera->setTarget(position + _calcDirection());
};

void FPCameraController::strafe(const float amount)
{
	Vect3 dir = camera->getTarget() - camera->getPosition();
	Vect3 right = math::cross(camera->getUp(), dir);
	right.normalize();
	right *= amount;
	position = position + right;
	camera->setPosition(position);
	camera->setTarget(position + dir);
};

const shared_ptr<Camera>& FPCameraController::getCamera() const
{
	return camera;
};

void FPCameraController::setNewAspect(const unsigned int new_width, const unsigned new_height)
{
	camera->setAspect((float)new_width/(float)new_height);
};