///////////////////////////////////////////////////////////////////////////////
//  CProjection.cpp

#include "CCamera.h"
#include "CProjection.h"

namespace math3d {

CProjection::CProjection(void) :
m_Width(0),
m_Height(0),
m_fNear(0.0f),
m_fFar(0.0f),
m_Deg(0.0f)
{
}

CProjection::~CProjection()
{
}

void CProjection::Set(int nWidth, int nHeight, float fNear, float fFar, float fDeg)
{
	m_Width = nWidth;
	m_Height = nHeight;
	m_fNear = fNear;
	m_fFar = fFar;
	m_Deg = fDeg;

	const double dN = m_fNear;
	const double dF = m_fFar;

	if (m_Deg > 0.0f) {
		// 縦長比率の場合は指定角度を垂直角度とみなす
		if (nHeight > nWidth) {
			fDeg = fDeg * nWidth / nHeight;
		}

		//
		const double dPI = 3.1415926535897932384626433;
		const double dHalfDeg = (double)fDeg * 0.5;
		const double dTan = tan(dHalfDeg * dPI / 180.0);

		const double dW = 1.0 / dTan;
		const double dH = (double)m_Width / (dTan * (double)m_Height);

		m_matProjection.Set(
			(float)dW, 0, 0, 0,
			0, (float)dH, 0, 0,
			0, 0, float(dF / (dF - dN)), 1,
			0, 0, float(-dN * dF / (dF - dN)), 0);
	}
	else {
		const double dMag = -m_Deg;		// 拡大率

		m_matProjection.Set(
			float(dMag), 0, 0, 0,
			0, float(dMag * m_Width / m_Height), 0, 0,
			0, 0, float(1.0 / (dF - dN)), 0,
			0, 0, 0, 1);
	}

	// 各平面の外向きの法線を求める
	m_NormalList.resize(6);

	m_NormalList[0] = S3D(-m_matProjection.m[0][0], 0.0f, -1.0f).GetNormalize();	// Left
	m_NormalList[1] = S3D( m_matProjection.m[0][0], 0.0f, -1.0f).GetNormalize();	// Right
	m_NormalList[2] = S3D(0.0f,  m_matProjection.m[1][1], -1.0f).GetNormalize();	// Top
	m_NormalList[3] = S3D(0.0f, -m_matProjection.m[1][1], -1.0f).GetNormalize();	// Bottom
	m_NormalList[4] = S3D(0.0f, 0.0f, -1.0f);										// Near
	m_NormalList[5] = S3D(0.0f, 0.0f,  1.0f);										// Far
}

void CProjection::Resize(int Width, int Height)
{
	Set(Width, Height, m_fNear, m_fFar, m_Deg);
}

int CProjection::GetWidth() const
{
	return m_Width;
}

int CProjection::GetHeight() const
{
	return m_Height;
}

float CProjection::GetNear() const
{
	return m_fNear;
}

float CProjection::GetFar() const
{
	return m_fFar;
}

float CProjection::GetDeg() const
{
	return m_Deg;
}

const SMatrix& CProjection::GetMatrix() const
{
	return m_matProjection;
}

const std::vector<S3D>& CProjection::GetNormalList() const
{
	return m_NormalList;
}

S3D CProjection::GetPosOnNearPlane(int X, int Y) const
{
	const float TanX = 1.0f / m_matProjection.m[0][0];
	const float TanY = 1.0f / m_matProjection.m[1][1];

	const float X0 = m_fNear * -TanX;
	const float X1 = m_fNear *  TanX;
	const float Y0 = m_fNear *  TanY;
	const float Y1 = m_fNear * -TanY;

	const float XRate = X / static_cast<float>(m_Width);
	const float YRate = Y / static_cast<float>(m_Height);

	return S3D(
		X0 + (X1 - X0) * XRate,
		Y0 + (Y1 - Y0) * YRate,
		m_fNear);
}

// ビューフラスタム
std::vector<SPlane> CProjection::CalcViewFrustumPlaneList(const CCamera& Camera) const
{
	std::vector<SPlane> ViewFrustumPlaneList;
	ViewFrustumPlaneList.resize(6);

	const auto& ViewFrustumNormalList = GetNormalList();

	for (int n = 0; n < 4; n++) {
		ViewFrustumPlaneList[n] = SPlane(Camera.GetPos(), ViewFrustumNormalList[n].MulMatrixRot(Camera.GetRotInvMatrix()));
	}

	ViewFrustumPlaneList[4] = SPlane(Camera.GetPos() + Camera.GetViewVec() * GetNear(),
		S3D(0.0f, 0.0f, -1.0f).MulMatrixRot(Camera.GetRotInvMatrix()));
	ViewFrustumPlaneList[5] = SPlane(Camera.GetPos() + Camera.GetViewVec() * GetFar(),
		S3D(0.0f, 0.0f, 1.0f).MulMatrixRot(Camera.GetRotInvMatrix()));

	return ViewFrustumPlaneList;
}

//
SMatrix CProjection::CalcFOVMatrix(float UpTan, float DownTan, float LeftTan, float RightTan, float Near, float Far)
{
	float XScale = 2.0f / ( LeftTan + RightTan );
	float XOffset = ( LeftTan - RightTan ) * XScale * 0.5f;
	float YScale = 2.0f / ( UpTan + DownTan );
	float YOffset = ( UpTan - DownTan ) * YScale * 0.5f;

	const float Hand = 1.0f;

	return SMatrix(
		XScale,				0.0f,				0.0f,							0.0f,
		0.0f,				YScale,				0.0f,							0.0f,
		XOffset * Hand,		Hand * -YOffset,	-Hand * Far / (Near - Far),		Hand,
		0.0f,				0.0f,				(Far * Near) / (Near - Far),	0.0f);
}

}

