#include "stdafx.h"
#include "RenderViewCamera.h"

namespace Assets
{
	RenderViewCamera::RenderViewCamera()
		: _Theta(0), _Phi(0), _Radius(0), _Up(1), _Target(0, 0, 0), _ViewMatrix(), _ProjectionMatrix(), _MatrixDirty(true)
	{
	}

	RenderViewCamera::RenderViewCamera(float Theta, float Phi, float Radius, RenderViewCameraUpAxis UpAxis)
		: _Theta(Theta), _Phi(Phi), _Radius(Radius), _Up(1), _Target(0, 0, 0), _ViewMatrix(), _ProjectionMatrix(), _MatrixDirty(true)
	{
	}

	void RenderViewCamera::Rotate(float Theta, float Phi)
	{
		this->_MatrixDirty = true;

		if (_Up > 0.f)
			_Theta += Theta;
		else
			_Theta -= Theta;

		_Phi += Phi;

		if (_Phi > (float)MathHelper::PI2)
			_Phi -= (float)MathHelper::PI2;
		else if (_Phi < -(float)MathHelper::PI2)
			_Phi += (float)MathHelper::PI2;

		if ((_Phi > 0 && _Phi < (float)MathHelper::PI) || (_Phi < -(float)MathHelper::PI && _Phi > -(float)MathHelper::PI2))
			_Up = 1.f;
		else
			_Up = -1.f;
	}

	void RenderViewCamera::Zoom(float Distance)
	{
		this->_MatrixDirty = true;

		_Radius -= Distance;

		if (_Radius <= 0.f)
		{
			_Radius = 30.f;
			auto Look = (_Target - this->GetCameraPosition()).GetNormalized();
			_Target = (_Target + (Look * 30.f));
		}
	}

	void RenderViewCamera::Pan(float X, float Y)
	{
		this->_MatrixDirty = true;

		auto Look = (_Target - this->GetCameraPosition()).GetNormalized();
		auto WorldUp = Vector3(0, _Up, 0);

		auto Right = Look.Cross(WorldUp);
		auto Up = Look.Cross(Right);

		_Target = (_Target + ((Right * X) + (Up * Y)));
	}

	void RenderViewCamera::Reset(float Theta, float Phi, float Radius)
	{
		_Theta = Theta;
		_Phi = Phi;
		_Radius = Radius;
		_Up = 1;
		_Target = { 0, 0, 0 };
		_ViewMatrix = {};
		_ProjectionMatrix = {};
		_MatrixDirty = true;
	}

	void RenderViewCamera::SetUpAxis(RenderViewCameraUpAxis UpAxis)
	{
		switch (UpAxis)
		{
		case RenderViewCameraUpAxis::Y:
			_ModelMatrix = Matrix();
			break;
		case RenderViewCameraUpAxis::Z:
			_ModelMatrix = Matrix::CreateFromQuaternion(Quaternion::FromEulerAngles(-90, 0, 0));
			break;
		}
		_MatrixDirty = true;
	}

	void RenderViewCamera::UpdateProjectionMatrix(float Fov, float ClientWidth, float ClientHeight, float NearClip, float FarClip)
	{
		this->_ProjectionMatrix = Matrix::CreatePerspectiveFov(Fov, ClientWidth / ClientHeight, NearClip, FarClip);
	}

	Vector3 RenderViewCamera::GetCameraPosition() const
	{
		return (this->_Target + this->ToCartesian());
	}

	Matrix& RenderViewCamera::GetProjectionMatrix()
	{
		return this->_ProjectionMatrix;
	}

	Matrix& RenderViewCamera::GetViewMatrix()
	{
		if (this->_MatrixDirty)
		{
			this->UpdateViewMatrix();
			this->_MatrixDirty = false;
		}

		return this->_ViewMatrix;
	}

	Matrix& RenderViewCamera::GetModelMatrix()
	{
		return this->_ModelMatrix;
	}

	void RenderViewCamera::UpdateViewMatrix()
	{
		this->_ViewMatrix = Matrix::CreateLookAt(GetCameraPosition(), this->_Target, Vector3(0.f, this->_Up, 0.f));
	}

	Vector3 RenderViewCamera::ToCartesian() const
	{
		return Vector3(
			_Radius * sinf(_Phi) * sinf(_Theta),
			_Radius * cosf(_Phi),
			_Radius * sinf(_Phi) * cosf(_Theta)
		);
	}
}