///////////////////////////////////////////////////////////////////////////////
/// @file deCamera.cpp
///
/// @brief Viewport & Rendering Data
///
/// @author Assassin
///
/// This file is the intellectual property of Novus Delta, LLC.. Usage of the
/// contents of this file is subject to the Destiny3D Member License which
/// can be found at http://www.destiny3d.com.  Any other usage is prohibited.
///
/// This file is distributed "AS IS" without warranty of any kind.  Novus
/// Delta, LLC. does not guarantee the fitness of the contents of this file
/// for any particular purpose.
///
/// Copyright (C) 2001-2003 Novus Delta, LLC. All Rights Reserved.
///
/// <hr>
///                                 Change History
/// <hr>
///
/// @date Jan 2002
/// @author Assassin
/// @remarks Creation
///
///////////////////////////////////////////////////////////////////////////////

#include "deCamera_priv.hpp"
#include "deMath.hpp"

DECAMERA_API IdeCamera * IdeCamera_CreateCamera()
{
	return new deCamera;
}

DECAMERA_API IdeCamera * IdeCamera_CreateCameraFromRef(IdeCamera* ref)
{
	return new deCamera(*((deCamera*)(ref)));
}

deCamera::deCamera()
{
	m_FOV = 90;
	m_FOVy = 90;
	m_hWnd = NULL;
	m_Viewport.Left = m_Viewport.Top = 0;
	m_Viewport.Right = m_Viewport.Bottom = 100;
	m_LastAspect = -1;
	m_PixelAspect = 1.0;
	m_AspectLocked = deFALSE;
	m_OrthoWidth = 1;
	m_IsOrtho = deFALSE;
	m_NearClip = 0.1;
	m_FarClip = 1000;
	//m_Position.Reset(); //not needed, defaults to identity
	m_FogEnable = deFALSE;
	m_RangeFog = deFALSE;
	m_NearFog = 10;
	m_FarFog = 1000;
	m_FogColor = 0x00808080;
}

deCamera::deCamera(const deCamera & ref)
{
	m_FOV = ref.m_FOV;
	m_FOVy = ref.m_FOVy;
	m_hWnd = ref.m_hWnd;
	m_Viewport = ref.m_Viewport;
	m_LastAspect = ref.m_LastAspect;
	m_PixelAspect = ref.m_PixelAspect;
	m_AspectLocked = ref.m_AspectLocked;
	m_OrthoWidth = ref.m_OrthoWidth;
	m_IsOrtho = ref.m_IsOrtho;
	m_NearClip = ref.m_NearClip;
	m_FarClip = ref.m_FarClip;
	m_Position = ref.m_Position;
	m_FogEnable = ref.m_FogEnable;
	m_RangeFog = ref.m_RangeFog;
	m_NearFog = ref.m_NearFog;
	m_FarFog = ref.m_FarFog;
	m_FogColor = ref.m_FogColor;
}

deCamera::~deCamera()
{
}

long deCamera::Release()
{
	delete this;
	return 0;
}

deTransformInfo& deCamera::Position()
{
	return m_Position;
}

void deCamera::SetRect(deRect rect)
{
	m_Viewport = rect;
}

void deCamera::SetWindow(HWND window)
{
	m_hWnd = window;
}

void deCamera::SetFOV(deDouble Degrees)
{
	m_FOV = Degrees;
	if (m_AspectLocked)
		m_FOVy = 2 * ATAN(TAN(m_FOV*(DE_PI/360))/m_LastAspect);
	else
		m_LastAspect = -1;
	m_IsOrtho = DE_FALSE;
}

void deCamera::SetScreenAspect(long PixelWidth, long PixelHeight, deDouble AspectRatio)
{
	m_PixelAspect = (AspectRatio * PixelHeight) / (PixelWidth);
	m_LastAspect = -1;
}

void deCamera::SetClip(deDouble Near, deDouble Far)
{
	m_NearClip = Near;
	m_FarClip = Far;
}

void deCamera::FogEnable(deBoolean Enable)
{
	m_FogEnable = Enable;
}

void deCamera::SetFog(deFloat Near, deFloat Far, deARGB Color, deBoolean RangeFog)
{
	m_NearFog = Near;
	m_FarFog = Far;
	m_FogColor = Color;
	m_RangeFog = RangeFog;
	m_FogEnable = deTRUE;
}

void deCamera::SetOrthoWidth(deDouble Width)
{
	m_OrthoWidth = Width;
	m_IsOrtho = DE_TRUE;
}

void deCamera::EnableOrtho(deBoolean OrthoEnable)
{
	m_IsOrtho = OrthoEnable;
}

deRect deCamera::GetRect()
{
	return m_Viewport;
}

HWND deCamera::GetWindow()
{
	return m_hWnd;
}

deDouble deCamera::GetFOV()
{
	return m_FOV;
}

deDouble deCamera::GetNearClip()
{
	return m_NearClip;
}

deDouble deCamera::GetFarClip()
{
	return m_FarClip;
}

deDouble deCamera::GetOrthoWidth()
{
	return m_OrthoWidth;
}

deBoolean deCamera::IsOrtho()
{
	return m_IsOrtho;
}

deBoolean deCamera::GetFog(deFloat& Near, deFloat& Far, deARGB& Color, deBoolean& RangeFog)
{
	if (!m_FogEnable)
		return deFALSE;
	Near = m_NearFog;
	Far = m_FarFog;
	Color = m_FogColor;
	RangeFog = m_RangeFog;
	return deTRUE;
}

// this is here because direct3d is STUPID and wants vertical FOV
// and games like to use horizontal FOV
deDouble deCamera::GetFOVy(deDouble &Aspect)
{
	if (m_AspectLocked)
	{
		Aspect = m_LastAspect;
		return m_FOVy;
	}
	Aspect *= m_PixelAspect;
	if (Aspect != m_LastAspect)
	{
		//Vertical = 2 * arctan (tan(Horizontal / 2) / aspect)
		m_FOVy = 2 * ATAN(TAN(m_FOV*(DE_PI/360))/(Aspect));
		m_LastAspect = Aspect;
	}
	return m_FOVy;
}

void deCamera::LockAspect(deDouble Aspect)
{
	if (Aspect <= 0)
	{
		m_LastAspect = -1;
		m_AspectLocked = deFALSE;
	}
	else
	{
		m_LastAspect = Aspect;
		m_FOVy = 2 * ATAN(TAN(m_FOV*(DE_PI/360))/(Aspect));
		m_AspectLocked = deTRUE;
	}
}

/*
void deCamera::BuildRootFrustum(deFrustum &f)
{
	deDouble	AngleX, AngleY, MidX, MidY, Aspect;
	int			Width, Height;

	Width = m_Viewport.Right - m_Viewport.Left;
	Height = m_Viewport.Bottom - m_Viewport.Top;
	Aspect = ((deDouble)Width)/Height;

	if (m_Ortho)
	{
		f.Ortho = DE_TRUE;
		f.LeftTan = -m_OrthoWidth/2;
		f.RightTan = m_OrthoWidth/2;
		f.TopTan = -m_OrthoWidth/(Aspect*2);
		f.BottomTan = m_OrthoWidth/(Aspect*2);
	}
	else
	{
		deDouble Multiplier;
		f.Ortho = DE_FALSE;

		Multiplier = m_FOV / 45.0;

		MidX = (m_Viewport.Right + m_Viewport.Left) / 2.0f;
		MidY = (m_Viewport.Bottom + m_Viewport.Top) / 2.0f;

		AngleX = (Multiplier/Width)*(m_Viewport.Left-MidX);
		f.LeftTan = (AngleX);
		
		AngleX = (Multiplier/Width)*(m_Viewport.Right-MidX);
		f.RightTan = (AngleX);
		
		AngleY = (Multiplier/Height)*(m_Viewport.Top-MidY);
		f.TopTan = (AngleY)/Aspect;
		
		AngleY = (Multiplier/Height)*(m_Viewport.Bottom-MidY);
		f.BottomTan = (AngleY)/Aspect;
	}

	f.MinDist = m_NearClip;
	f.MaxDist = m_FarClip;
}
*/

deBoolean deCamera::BuildRootFrustum(IdePlaneFrustum * pFrustum)
{
	if (!pFrustum)
		DEBUGRETURN(deFALSE, "BuildRootFrustum: Parameter 'pFrustum' was NULL");

	pFrustum->SetNumPlanes(4);
	pFrustum->SetDists(m_NearClip, m_FarClip);
	pFrustum->SetOrtho(m_IsOrtho);

	dePlane Plane;
	deVec3d CamPos, Normal;
	deDouble AngleX = DEG2RAD(m_FOV/2);
	deTransform Rotater(m_Position);

	Rotater.Translation = deZeroVec;
	CamPos = m_Position.Translation;

	Normal.z = SIN(AngleX);
	Normal.x = COS(AngleX);
	Normal.y = 0;
	Rotater.Transform(Normal, Plane.Norm);
	Plane.Dist = Vec3d_Dot(Plane.Norm, CamPos);
	pFrustum->SetPlane(0, Plane);

	Normal.x *= -1;
	Rotater.Transform(Normal, Plane.Norm);
	Plane.Dist = Vec3d_Dot(Plane.Norm, CamPos);
	pFrustum->SetPlane(1, Plane);

	Normal.z = SIN(m_FOVy/2);
	Normal.x = 0;
	Normal.y = COS(m_FOVy/2);
	Rotater.Transform(Normal, Plane.Norm);
	Plane.Dist = Vec3d_Dot(Plane.Norm, CamPos);
	pFrustum->SetPlane(2, Plane);
	
	Normal.y *= -1;
	Rotater.Transform(Normal, Plane.Norm);
	Plane.Dist = Vec3d_Dot(Plane.Norm, CamPos);
	pFrustum->SetPlane(3, Plane);

	pFrustum->SetInVec(m_Position.GetIn());
	pFrustum->SetPosVec(m_Position.Translation);

	return deTRUE;
}

deVec3d deCamera::ScreenPointToWorldVec(int x, int y, deVec3d *StartPosition)
{
	deDouble	AngleX, AngleY, MidX, MidY, Aspect;
	deDouble	Multiplier;
	int			Width, Height;
	deVec3d		RetVec;
	deTransform	M(m_Position);

	// calculate some values
	Width = m_Viewport.Right - m_Viewport.Left;
	Height = m_Viewport.Bottom - m_Viewport.Top;
	Aspect = ((deDouble)Width)/Height;
	MidX = (m_Viewport.Right + m_Viewport.Left) / 2.0f;
	MidY = (m_Viewport.Bottom + m_Viewport.Top) / 2.0f;

	// remember that this will give you the ACTUAL SCREEN POSITION in the world
	if (m_IsOrtho)
	{
		deDouble X=x,Y=y;
		X = X - MidX;
		Y = MidY - Y;
		X = X*(m_OrthoWidth/(Width));
		Y = -Y*(m_OrthoWidth/(Height*Aspect));
		// gotta have the start position for
		if (StartPosition == NULL)
			return deZeroVec;
		*StartPosition = m_Position.Translation;
		*StartPosition = *StartPosition + (m_Position.GetRight() * X);
		*StartPosition = *StartPosition + (m_Position.GetUp() * Y);
		RetVec = m_Position.GetIn();
		Vec3d_Normalize(RetVec);
		return RetVec;
	}

	// extract camera attributes
	Multiplier = -180.0 / m_FOV;

	// figure out what angles we're pointing at
	AngleX = Multiplier*(x-MidX)/((deDouble)Width);
	Multiplier /= Aspect;
	AngleY = Multiplier*(y-MidY)/((deDouble)Height);

	RetVec.x = (AngleX);
	RetVec.y = (AngleY);
	RetVec.z = 1;
	// let user normalize return value if they want
//	Vec3d_Normalize(RetVec);

	// rotate the vector into local camera space
	if (StartPosition)
		*StartPosition = M.Translation;
	M.Translation = deZeroVec;
	M.Transform(RetVec, RetVec);

	return RetVec;
}

deBoolean deCamera::WorldPosToScreenPos(const deVec3d &pos, int &x, int &y)
{
	// some intermediate values
	int Width = (m_Viewport.Right - m_Viewport.Left);
	int Height = (m_Viewport.Bottom - m_Viewport.Top);
	double H, W, FOVY;
	double Aspect = ((deDouble)Width)/Height;
	FOVY = GetFOVy(Aspect);
	FOVY *= 0.5f;
	H = cos(FOVY) / sin(FOVY);
	W = H / Aspect;
	
	//setup the matrix
	deMatrix44 FOVMatrix;
	memset(FOVMatrix.m, 0, 16*sizeof(FOVMatrix._00));
	
	FOVMatrix._00 = W;
	FOVMatrix._11 = H;
	FOVMatrix._22 = m_FarClip/(m_FarClip-m_NearClip);
	FOVMatrix._23 = 1.0f;
	FOVMatrix._32 = -m_NearClip * FOVMatrix._22;

	deVec4d output;
	deVec3d localpos;// = pos;
	m_Position.TransposeTransform(pos, localpos);
	FOVMatrix.Transform(localpos, output);

	if (output.z <= 0)
		return deFALSE;

	output.x /= output.w;
	output.y /= output.w;
	
	if (output.x < -1 || output.x > 1 || output.y < -1 || output.y > 1)
		return deFALSE;

	x = int(m_Viewport.Left + Width * (output.x + 1) / 2);
	y = int(m_Viewport.Top + Height * (1 - output.y) / 2);

	return deTRUE;
}

	/*
	deDouble	MidX, MidY, Aspect;
	int			Width, Height;
	deRect		Viewport;
	deDouble	Multiplier;
	deVec3d		NewPos;
	deTransform	M;

	Viewport = m_Viewport;

	// calc some values
	Width = Viewport.Right - Viewport.Left;
	Height = Viewport.Bottom - Viewport.Top;
	Aspect = ((deDouble)Width)/Height;
	MidX = (Viewport.Right + Viewport.Left) / 2.0f;
	MidY = (Viewport.Bottom + Viewport.Top) / 2.0f;

	// transform the world vector into local cam space
	m_Position.GetTranspose(M);
	M.Transform(pos, NewPos);

	if (NewPos.z <= 0)
	{
		x = y = -1;
		return;
	}

	if (m_IsOrtho)
	{
		// the +0.5 is to make it round off
		x = int((NewPos.x / m_OrthoWidth)*Width + MidX + 0.5);
		y = int((-NewPos.y * (Aspect/m_OrthoWidth))*Height + MidY + 0.5);
		return;
	}

	Multiplier = m_FOV / -180.0;

	x = (int)(Multiplier*Width*NewPos.x/NewPos.z + MidX);
	Multiplier *= Aspect;
	y = (int)(Multiplier*Height*NewPos.y/NewPos.z + MidY);
	
}*/

/*
deBoolean deCamera::FrustumRect(const deFrustum & f, deRect & SubRect)
{
	int Width, Height;
	deDouble Aspect, MidX, MidY;
	deFloatRect FSubRect;
	
	MidX = (m_Viewport.Right + m_Viewport.Left) / 2.0f;
	MidY = (m_Viewport.Bottom + m_Viewport.Top) / 2.0f;
	Width = m_Viewport.Right - m_Viewport.Left;
	Height = m_Viewport.Bottom - m_Viewport.Top;
	Aspect = ((deDouble)Width)/Height;
	
	if (f.Ortho)
	{
		return deFALSE;
	}
	else
	{
		deDouble Multiplier = 45.0 / m_FOV;

		FSubRect.Left = (deFloat)((f.LeftTan)*Multiplier);
		FSubRect.Right = (deFloat)((f.RightTan)*Multiplier);
		FSubRect.Bottom = (deFloat)((f.BottomTan*Aspect)*Multiplier);
		FSubRect.Top = (deFloat)((f.TopTan*Aspect)*Multiplier);
	}
	SubRect.Top = (int)(-Height*(FSubRect.Bottom - 1) - m_Viewport.Top - MidY);
	SubRect.Left = (int)(Width*(FSubRect.Left) - m_Viewport.Left + MidX);
	SubRect.Right = (int)(Width*(FSubRect.Right - 1) + m_Viewport.Right + MidX + 1);
	SubRect.Bottom = (int)(-Height*(FSubRect.Top) + m_Viewport.Bottom - MidY + 1);

	if (SubRect.Top < 0)
		SubRect.Top = 0;
	if (SubRect.Left < 0)
		SubRect.Left = 0;
	if (SubRect.Right > m_Viewport.Right)
		SubRect.Right = m_Viewport.Right;
	if (SubRect.Bottom > m_Viewport.Bottom)
		SubRect.Bottom = m_Viewport.Bottom;
	
	return deTRUE;
}
*/

//deCamera::FrustumRect moved to deFrustum.cpp
