/* -----------------------------------------------------------------------------
	GSFramework
	Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
----------------------------------------------------------------------------- */


		#include "core/camera.h"
		#include "core/light.h"
		#include "log/log.h"

		using namespace GS;
		using namespace GS::Core;


//------------------------------------------------------------------------------
void				Camera::AlignTo(const Light &l)
{
	aspect_ratio = 1;
	SnapshotTransformation(l.GetMatrix());

	SetNearClippingPlane(l.GetNearClippingPlane());
	SetFarClippingPlane(l.GetFarClippingPlane());

	float fov = l.cone_angle + l.edge_angle;
	SetZoomFactor(Math::Cos(fov) / Math::Sin(fov));
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
bool				Camera::WorldToScreen(const fRect &v, const Vector4 &in, Vector4 &out, bool normalize)
{
	Vector4 dp = in * GetInverseMatrix();
	if	(dp.z <= 0)
		return false;

	dp.x /= dp.z / zoom_factor;
	dp.y /= dp.z / zoom_factor;

	if	(normalize)
	{
		if	(aspect_ratio_ref_yaxis)
				dp.x /= v.GetWidth() / v.GetHeight();
		else	dp.y /= v.GetWidth() / v.GetHeight();
	}

	out.Set((1 + dp.x) * 0.5f, (1 - dp.y) * 0.5f, 0);
	return true;
}
Vector4				Camera::ScreenToWorld(const fRect &v, float x, float y, float z, float ar)
{
	Vector4 sw((x - 0.5f) * 2.f, -(y - 0.5f) * 2.f, zoom_factor);
	sw *= z / sw.z;

	if	(ar < 0)
	{
		if	(aspect_ratio_ref_yaxis)
				sw.x *= v.GetWidth() / v.GetHeight();
		else	sw.y *= v.GetWidth() / v.GetHeight();
	}
	else
		sw.x *= ar;

	return sw * GetMatrix();
}
Vector4				Camera::ComputeAspectRatioCorrection(const fRect &v, float global_ar) const
{
	float k_ar = aspect_ratio;

	if	(k_ar <= 0.f)	// Square AR.
		k_ar = v.GetHeight() / v.GetWidth();
	k_ar *= global_ar;

	if	(aspect_ratio_ref_yaxis)
		return Vector4(k_ar, 1, 1);
	return Vector4(1, 1.f / k_ar, 1);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Camera::ComputeProjectionMatrix(const fRect &v, Matrix4 &m) const
{
#if		__PLATFORM_NINTENDO_WII__
	const float	za = z_far;
#else
	const float	za = z_near;
#endif

	if (is_occulus_camera)
	{
		m = custom_projection;
/*		// create scale and offset
		float projXScale = 2.0f / (tan_left + tan_right);
		float projXOffset = (tan_left - tan_right) * projXScale * 0.5f;
		float projYScale = 2.0f / (tan_up + tan_down);
		float projYOffset = (tan_up - tan_down) * projYScale * 0.5f;

		//result.Scale = GS::Vector2(projXScale, projYScale);
		//result.Offset = GS::Vector2(projXOffset, projYOffset);
		// Hey - why is that Y.Offset negated?
		// It's because a projection matrix transforms from world coords with Y=up,
		// whereas this is from NDC which is Y=down.

		m.Set
			(
			projXScale, 0, 0, 0,
			0, projYScale, 0, 0,
			0, 0, z_far / (z_far - z_near), 1,
			projXOffset, -projYOffset, -(z_far * z_near) / (z_far - z_near), 0
			);
		/*
		float idx = 1.0f / (tan_right - tan_left);
		float idy = 1.0f / (tan_down - tan_up);
		float idz = 1.0f / (z_far - z_near);
		float sx = tan_right + tan_left;
		float sy = tan_down + tan_up;

		m.Set
			(
			2 * idx, 0, 0, 0,
			0, 2 * idy, 0, 0,
			0, 0, z_far / (z_far - z_near), 1,
			sx*idx, -sy*idy, -(z_far * z_near) / (z_far - z_near), 0
			);
		*/
		/*
		float(*p)[4] = pmProj->m;
		p[0][0] = 2 * idx; p[0][1] = 0;     p[0][2] = sx*idx;    p[0][3] = 0;
		p[1][0] = 0;     p[1][1] = 2 * idy; p[1][2] = sy*idy;    p[1][3] = 0;
		p[2][0] = 0;     p[2][1] = 0;     p[2][2] = -zFar*idz; p[2][3] = -zFar*zNear*idz;
		p[3][0] = 0;     p[3][1] = 0;     p[3][2] = -1.0f;     p[3][3] = 0;
		*/
	}
	else
	{
		if (is_orthographic)
		{
			const float	q = 1.f / (z_far - z_near);

			m.Set
				(
				2.f / ortho_w, 0, 0, 0,
				0, 2.f / ortho_h, 0, 0,
				0, 0, q, 0,
				0, 0, -q * za, 1
				);
		}
		else
		{
			m.Set
				(
				zoom_factor, 0, 0, 0,
				0, zoom_factor, 0, 0,
				0, 0, z_far / (z_far - z_near), 1,
				0, 0, -(z_far * z_near) / (z_far - z_near), 0
				);
		}

		Vector4 ar = ComputeAspectRatioCorrection(v);
		m.m[0][0] *= ar.x;
		m.m[1][1] *= ar.y;
	}
}
void				Camera::ComputeFrustum(Frustum &f, const fRect &viewport, float zn, float zf) const
{
	Vector4 k_ar = ComputeAspectRatioCorrection(viewport);

	if	(is_orthographic)
			f.SetOrthographic(ortho_w, ortho_h, zn != -1 ? zn : z_near, zf != -1 ? zf : z_far, &GetMatrix(), k_ar.x, k_ar.y);
	else	f.SetPerspective(GetFov(), zn != -1 ? zn : z_near, zf != -1 ? zf : z_far, &GetMatrix(), k_ar.x, k_ar.y);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				Camera::ComputeMatrix()
{
	bool update_world = item_flags.IsSet(ItemFlagWorldMatrixDirty);

	Item::ComputeMatrix();

	// Remove scale from world matrix.
	if	(update_world)
	{
		Vector4 u = matrix.GetRow(0).Normalized();
		matrix.m[0][0] = u.x; matrix.m[1][0] = u.y; matrix.m[2][0] = u.z;
		Vector4 v = matrix.GetRow(1).Normalized();
		matrix.m[0][1] = v.x; matrix.m[1][1] = v.y; matrix.m[2][1] = v.z;
		Vector4 w = matrix.GetRow(2).Normalized();
		matrix.m[0][2] = w.x; matrix.m[1][2] = w.y; matrix.m[2][2] = w.z;
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
float				Camera::GetFov() const
{	return Math::ATan(1.f / zoom_factor) * 2.f;	}
void				Camera::SetZoomFactor(float z)
{	zoom_factor = z; /*Types::Max(0.1f, z);*/	}
void				Camera::SetFov(float fov)
{
	fov = Types::Clamp(fov, Units::Deg(0.5f), Units::Deg(179.95f));
	SetZoomFactor(1.f / Math::Tan(fov * 0.5f));
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Camera::Camera()
{
	using namespace Units;

	SetZoomFactor(3.2f);

	is_orthographic = false;
	ortho_w = Mtr(1.f);
	ortho_h = Mtr(1.f);

	z_near = Cm(10.f);
	z_far = Km(50.f);

	aspect_ratio = -1.f;
	aspect_ratio_ref_yaxis = true;

	is_occulus_camera = false;
}
//------------------------------------------------------------------------------
