//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Implements a camera for the 3D view.
//
// $NoKeywords: $
//=============================================================================//

#include <windows.h>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include "Camera.h"
#include "hammer_mathlib.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

//
// Indices of camera axes.
//
#define CAMERA_RIGHT		0
#define CAMERA_UP			1
#define CAMERA_FORWARD		2
#define CAMERA_ORIGIN		3

#define MIN_PITCH		-90.0f
#define MAX_PITCH		90.0f


static void DBG(PRINTF_FORMAT_STRING const char *fmt, ...)
{
    char ach[128];
    va_list va;

    va_start(va, fmt);
    vsprintf(ach, fmt, va);
    va_end(va);
    OutputDebugString(ach);
}


//-----------------------------------------------------------------------------
// Purpose: Constructor.
//-----------------------------------------------------------------------------
CCamera::CCamera(void)
{
	m_ViewPoint.Init();

	m_fPitch = 0.0;
	m_fRoll = 0.0;
	m_fYaw = 0.0;

	m_fHorizontalFOV = 90;
	m_fNearClip = 1.0;
	m_fFarClip = 5000;

    m_fZoom = 1.0f;
	m_bIsOrthographic = false;

	m_fScaleHorz = m_fScaleVert = 1;
	m_nViewWidth = m_nViewHeight = 100;

	BuildViewMatrix();
	BuildProjMatrix();
}


//-----------------------------------------------------------------------------
// Purpose: Destructor.
//-----------------------------------------------------------------------------
CCamera::~CCamera(void)
{
}

void CCamera::SetViewPort( int width, int height )
{
	if ( m_nViewWidth != width || m_nViewHeight != height )
	{
		m_nViewWidth = width;
		m_nViewHeight = height;
		BuildProjMatrix();
	}
}

void CCamera::GetViewPort( int &width, int &height )
{
	width = m_nViewWidth;
	height = m_nViewHeight;
}

//-----------------------------------------------------------------------------
// Purpose: Returns the current value of the camera's pitch.
//-----------------------------------------------------------------------------
float CCamera::GetPitch(void)
{
	return(m_fPitch);
}


//-----------------------------------------------------------------------------
// Purpose: Returns the current value of the camera's roll.
//-----------------------------------------------------------------------------
float CCamera::GetRoll(void)
{
	return(m_fRoll);
}


//-----------------------------------------------------------------------------
// Purpose: Returns the current value of the camera's yaw.
//-----------------------------------------------------------------------------
float CCamera::GetYaw(void)
{
	return(m_fYaw);
}


//-----------------------------------------------------------------------------
// Purpose: returns the camera angles
// Output : returns the camera angles
//-----------------------------------------------------------------------------
QAngle CCamera::GetAngles()
{
	return QAngle( m_fPitch, m_fYaw, m_fRoll );
}


//-----------------------------------------------------------------------------
// Purpose: Moves the camera along the camera's right axis.
// Input  : fUnits - World units to move the camera.
//-----------------------------------------------------------------------------
void CCamera::MoveRight(float fUnits)
{
	if (fUnits != 0)
	{
		m_ViewPoint[0] += m_ViewMatrix[CAMERA_RIGHT][0] * fUnits;
		m_ViewPoint[1] += m_ViewMatrix[CAMERA_RIGHT][1] * fUnits;
		m_ViewPoint[2] += m_ViewMatrix[CAMERA_RIGHT][2] * fUnits;
		BuildViewMatrix();
	}
}


//-----------------------------------------------------------------------------
// Purpose: Moves the camera along the camera's up axis.
// Input  : fUnits - World units to move the camera.
//-----------------------------------------------------------------------------
void CCamera::MoveUp(float fUnits)
{
	if (fUnits != 0)
	{
		m_ViewPoint[0] += m_ViewMatrix[CAMERA_UP][0] * fUnits;
		m_ViewPoint[1] += m_ViewMatrix[CAMERA_UP][1] * fUnits;
		m_ViewPoint[2] += m_ViewMatrix[CAMERA_UP][2] * fUnits;
		BuildViewMatrix();
	}
}


//-----------------------------------------------------------------------------
// Purpose: Moves the camera along the camera's forward axis.
// Input  : fUnits - World units to move the camera.
//-----------------------------------------------------------------------------
void CCamera::MoveForward(float fUnits)
{
	if (fUnits != 0)
	{
		m_ViewPoint[0] -= m_ViewMatrix[CAMERA_FORWARD][0] * fUnits;
		m_ViewPoint[1] -= m_ViewMatrix[CAMERA_FORWARD][1] * fUnits;
		m_ViewPoint[2] -= m_ViewMatrix[CAMERA_FORWARD][2] * fUnits;
		BuildViewMatrix();
	}
}

//-----------------------------------------------------------------------------
// Purpose: Returns the camera's viewpoint.
//-----------------------------------------------------------------------------
void CCamera::GetViewPoint(Vector& ViewPoint) const
{
	ViewPoint = m_ViewPoint;
}

//-----------------------------------------------------------------------------
// Purpose: Returns a vector indicating the camera's forward axis.
//-----------------------------------------------------------------------------
void CCamera::GetViewForward(Vector& ViewForward) const
{
	ViewForward[0] = -m_ViewMatrix[CAMERA_FORWARD][0];
	ViewForward[1] = -m_ViewMatrix[CAMERA_FORWARD][1];
	ViewForward[2] = -m_ViewMatrix[CAMERA_FORWARD][2];
}


//-----------------------------------------------------------------------------
// Purpose: Returns a vector indicating the camera's up axis.
//-----------------------------------------------------------------------------
void CCamera::GetViewUp(Vector& ViewUp) const
{
	ViewUp[0] = m_ViewMatrix[CAMERA_UP][0];
	ViewUp[1] = m_ViewMatrix[CAMERA_UP][1];
	ViewUp[2] = m_ViewMatrix[CAMERA_UP][2];
}


//-----------------------------------------------------------------------------
// Purpose: Returns a vector indicating the camera's right axis.
//-----------------------------------------------------------------------------
void CCamera::GetViewRight(Vector& ViewRight) const
{
	ViewRight[0] = m_ViewMatrix[CAMERA_RIGHT][0];
	ViewRight[1] = m_ViewMatrix[CAMERA_RIGHT][1];
	ViewRight[2] = m_ViewMatrix[CAMERA_RIGHT][2];
}

//-----------------------------------------------------------------------------
// Purpose: Returns the horizontal field of view in degrees.
//-----------------------------------------------------------------------------
float CCamera::GetFOV(void)
{
	return(m_fHorizontalFOV);
}


//-----------------------------------------------------------------------------
// Purpose: Returns the distance from the camera to the near clipping plane in world units.
//-----------------------------------------------------------------------------
float CCamera::GetNearClip(void)
{
	return(m_fNearClip);
}


//-----------------------------------------------------------------------------
// Purpose: Returns the distance from the camera to the far clipping plane in world units.
//-----------------------------------------------------------------------------
float CCamera::GetFarClip(void)
{
	return(m_fFarClip);
}

//-----------------------------------------------------------------------------
// Purpose: Sets up fields of view & clip plane distances for the view frustum.
// Input  : fHorizontalFOV - 
//			fVerticalFOV - 
//			fNearClip - 
//			fFarClip - 
//-----------------------------------------------------------------------------
void CCamera::SetPerspective(float fHorizontalFOV, float fNearClip, float fFarClip)
{
	m_bIsOrthographic = false;
	m_fHorizontalFOV = fHorizontalFOV;
	m_fNearClip = fNearClip;
	m_fFarClip = fFarClip;
	BuildProjMatrix();
}

void CCamera::SetOrthographic(float fZoom, float fNearClip, float fFarClip)
{
	m_fZoom = fZoom;
	m_fNearClip = fNearClip;
	m_fFarClip = fFarClip;
	m_bIsOrthographic = true;
	BuildProjMatrix();
}

void CCamera::GetFrustumPlanes( Vector4D Planes[6] )
{
	// TODO check for FrustumPlanesFromMatrix, maybe we can use that

	Vector ViewForward;
	GetViewForward(ViewForward);

	VMatrix CameraMatrix = m_ProjMatrix * m_ViewMatrix;

	//
	// Now the plane coefficients can be pulled directly out of the the camera
	// matrix as follows:
	//
	// Right :  first_column  - fourth_column
	// Left  : -first_column  - fourth_column
	// Top   :  second_column - fourth_column
	// Bottom: -second_column - fourth_column
	// Front : -third_column  - fourth_column
	// Back  :  third_column  + fourth_column
	//
	// dvs: My plane constants should be coming directly from the matrices,
	//		but they aren't (for some reason). Instead I calculate the plane
	//		constants myself. Sigh.
	//
	Planes[0][0] = CameraMatrix[0][0] - CameraMatrix[3][0];
	Planes[0][1] = CameraMatrix[0][1] - CameraMatrix[3][1];
	Planes[0][2] = CameraMatrix[0][2] - CameraMatrix[3][2];
	VectorNormalize(Planes[0].AsVector3D());
	Planes[0][3] = DotProduct(m_ViewPoint, Planes[0].AsVector3D());

	Planes[1][0] = -CameraMatrix[0][0] - CameraMatrix[3][0];
	Planes[1][1] = -CameraMatrix[0][1] - CameraMatrix[3][1];
	Planes[1][2] = -CameraMatrix[0][2] - CameraMatrix[3][2];
	VectorNormalize(Planes[1].AsVector3D());
	Planes[1][3] = DotProduct(m_ViewPoint, Planes[1].AsVector3D());

	Planes[2][0] = CameraMatrix[1][0] - CameraMatrix[3][0];
	Planes[2][1] = CameraMatrix[1][1] - CameraMatrix[3][1];
	Planes[2][2] = CameraMatrix[1][2] - CameraMatrix[3][2];
	VectorNormalize(Planes[2].AsVector3D());
	Planes[2][3] = DotProduct(m_ViewPoint, Planes[2].AsVector3D());

	Planes[3][0] = -CameraMatrix[1][0] - CameraMatrix[3][0];
	Planes[3][1] = -CameraMatrix[1][1] - CameraMatrix[3][1];
	Planes[3][2] = -CameraMatrix[1][2] - CameraMatrix[3][2];
	VectorNormalize(Planes[3].AsVector3D());
	Planes[3][3] = DotProduct(m_ViewPoint, Planes[3].AsVector3D());

	Planes[4][0] = -CameraMatrix[2][0] - CameraMatrix[3][0];
	Planes[4][1] = -CameraMatrix[2][1] - CameraMatrix[3][1];
	Planes[4][2] = -CameraMatrix[2][2] - CameraMatrix[3][2];
	VectorNormalize(Planes[4].AsVector3D());
	Planes[4][3] = DotProduct(m_ViewPoint + ViewForward * m_fNearClip, Planes[4].AsVector3D());

	Planes[5][0] = CameraMatrix[2][0] + CameraMatrix[3][0];
	Planes[5][1] = CameraMatrix[2][1] + CameraMatrix[3][1];
	Planes[5][2] = CameraMatrix[2][2] + CameraMatrix[3][2];
	VectorNormalize(Planes[5].AsVector3D());
	Planes[5][3] = DotProduct(m_ViewPoint + ViewForward * m_fFarClip, Planes[5].AsVector3D());
}

bool CCamera::IsOrthographic()
{
	return m_bIsOrthographic;
}

void CCamera::BuildProjMatrix()
{
	memset( &m_ProjMatrix,0,sizeof(m_ProjMatrix) );
	VMatrix &m = m_ProjMatrix;

	if ( m_bIsOrthographic )
	{
		// same as D3DXMatrixOrthoRH
		float w = (float)m_nViewWidth / m_fZoom;
		float h = (float)m_nViewHeight / m_fZoom;
	
		m[0][0] = 2/w;
		m[1][1] = 2/h;

		m[2][2] = 1/(m_fNearClip-m_fFarClip);
		m[2][3] = m_fNearClip/(m_fNearClip-m_fFarClip);

		m[3][3] = 1;
	}
	else
	{
		// same as D3DXMatrixPerspectiveRH
		float w = 2 * m_fNearClip * tan( m_fHorizontalFOV * M_PI / 360.0 );
		float h = ( w * float(m_nViewHeight) ) / float(m_nViewWidth);

		m[0][0] = 2*m_fNearClip/w;
		m[1][1] = 2*m_fNearClip/h;

		m[2][2] = m_fFarClip/(m_fNearClip-m_fFarClip);
		m[2][3] = (m_fNearClip*m_fFarClip)/(m_fNearClip-m_fFarClip);

		m[3][2] = -1;
	}

	m_ViewProjMatrix = m_ProjMatrix * m_ViewMatrix;
	m_ViewProjMatrix.InverseGeneral( m_InvViewProjMatrix );
}


//-----------------------------------------------------------------------------
// Purpose: Sets the distance from the camera to the near clipping plane in world units.
//-----------------------------------------------------------------------------
void CCamera::SetNearClip(float fNearClip)
{
	if ( m_fNearClip != fNearClip )
	{
		m_fNearClip = fNearClip;
		BuildProjMatrix();
	}
}


//-----------------------------------------------------------------------------
// Purpose: Sets the distance from the camera to the far clipping plane in world units.
//-----------------------------------------------------------------------------
void CCamera::SetFarClip(float fFarClip)
{
	if ( m_fFarClip != fFarClip )
	{
		m_fFarClip = fFarClip;
		BuildProjMatrix();
	}
}


//-----------------------------------------------------------------------------
// Purpose: Sets the pitch in degrees, from [MIN_PITCH..MAX_PITCH]
//-----------------------------------------------------------------------------
void CCamera::SetPitch(float fDegrees)
{
	if (m_fPitch != fDegrees)
	{
		m_fPitch = fDegrees;

		if (m_fPitch > MAX_PITCH)
		{
			m_fPitch = MAX_PITCH;
		}
		else if (m_fPitch < MIN_PITCH)
		{
			m_fPitch = MIN_PITCH;
		}
		BuildViewMatrix();
	}
}


//-----------------------------------------------------------------------------
// Purpose: Sets the roll in degrees, from [0..360)
//-----------------------------------------------------------------------------
void CCamera::SetRoll(float fDegrees)
{
	while (fDegrees >= 360)
	{
		fDegrees -= 360;
	}

	while (fDegrees < 0)
	{
		fDegrees += 360;
	}

	if (m_fRoll != fDegrees)
	{
		m_fRoll = fDegrees;
		BuildViewMatrix();
	}
}


//-----------------------------------------------------------------------------
// Purpose: Sets the yaw in degrees, from [0..360)
//-----------------------------------------------------------------------------
void CCamera::SetYaw(float fDegrees)
{
	while (fDegrees >= 360)
	{
		fDegrees -= 360;
	}

	while (fDegrees < 0)
	{
		fDegrees += 360;
	}

	if (m_fYaw != fDegrees)
	{
		m_fYaw = fDegrees;
		BuildViewMatrix();
	}
}


//-----------------------------------------------------------------------------
// Purpose: Sets the view point.
//-----------------------------------------------------------------------------
void CCamera::SetViewPoint(const Vector &ViewPoint)
{
	if ( m_ViewPoint != ViewPoint )
	{
		m_ViewPoint = ViewPoint;
		BuildViewMatrix();
	}
}


//-----------------------------------------------------------------------------
// Purpose: Sets the camera target, rebuilding the view matrix.
// Input  : ViewTarget - the point in world space that the camera should look at.
//-----------------------------------------------------------------------------
void CCamera::SetViewTarget(const Vector &ViewTarget)
{
	Vector ViewOrigin;
	Vector ViewForward;

	GetViewPoint( ViewOrigin );

	VectorSubtract(ViewTarget, ViewOrigin, ViewForward);
	VectorNormalize(ViewForward);

	//
	// Ideally we could replace the math below with standard VectorAngles stuff, but Hammer
	// camera matrices use a different convention from QAngle (sadly).
	//
	float fYaw = RAD2DEG(atan2(ViewForward[0], ViewForward[1]));
	SetYaw(fYaw);

	float fPitch = -RAD2DEG(asin(ViewForward[2]));
	SetPitch(fPitch);
}


//-----------------------------------------------------------------------------
// Purpose: Pitches the camera forward axis toward the camera's down axis a
//			given number of degrees.
//-----------------------------------------------------------------------------
void CCamera::Pitch(float fDegrees)
{
	if (fDegrees != 0)
	{
		float fPitch = GetPitch();
		fPitch += fDegrees;
		SetPitch(fPitch);
	}
}


//-----------------------------------------------------------------------------
// Purpose: Rolls the camera's right axis toward the camera's up axis a given
//			number of degrees.
//-----------------------------------------------------------------------------
void CCamera::Roll(float fDegrees)
{
	if (fDegrees != 0)
	{
		float fRoll = GetRoll();
		fRoll += fDegrees;
		SetRoll(fRoll);
	}
}


//-----------------------------------------------------------------------------
// Purpose: Yaws the camera's forward axis toward the camera's right axis a
//			given number of degrees.
//-----------------------------------------------------------------------------
void CCamera::Yaw(float fDegrees)
{
	if (fDegrees != 0)
	{
		float fYaw = GetYaw();
		fYaw += fDegrees;
		SetYaw(fYaw);
	}
}


//-----------------------------------------------------------------------------
// Purpose: Loads the given matrix with an identity matrix.
// Input  : Matrix - 4 x 4 matrix to be loaded with the identity matrix.
//-----------------------------------------------------------------------------
void CCamera::CameraIdentityMatrix(VMatrix& Matrix)
{
	// This function produces a transform which transforms from
	// material system camera space to quake camera space

	// Camera right axis lies along the world X axis.
	Matrix[CAMERA_RIGHT][0] = 1;
	Matrix[CAMERA_RIGHT][1] = 0;
	Matrix[CAMERA_RIGHT][2] = 0;
	Matrix[CAMERA_RIGHT][3] = 0;

	// Camera up axis lies along the world Z axis.
	Matrix[CAMERA_UP][0] = 0;
	Matrix[CAMERA_UP][1] = 0;
	Matrix[CAMERA_UP][2] = 1;
	Matrix[CAMERA_UP][3] = 0;

	// Camera forward axis lies along the negative world Y axis.
	Matrix[CAMERA_FORWARD][0] = 0;
	Matrix[CAMERA_FORWARD][1] = -1;
	Matrix[CAMERA_FORWARD][2] = 0;
	Matrix[CAMERA_FORWARD][3] = 0;

	Matrix[CAMERA_ORIGIN][0] = 0;
	Matrix[CAMERA_ORIGIN][1] = 0;
	Matrix[CAMERA_ORIGIN][2] = 0;
	Matrix[CAMERA_ORIGIN][3] = 1;
}

//-----------------------------------------------------------------------------
// Purpose: Generates a view matrix based on our current yaw, pitch, and roll.
//			The view matrix does not consider FOV or clip plane distances.
//-----------------------------------------------------------------------------
void CCamera::BuildViewMatrix()
{
	// The camera transformation is produced by multiplying roll * yaw * pitch.
	// This will transform a point from world space into quake camera space, 
	// which is exactly what we want for our view matrix. However, quake
	// camera space isn't the same as material system camera space, so
	// we're going to have to apply a transformation that goes from quake
	// camera space to material system camera space.
	
	CameraIdentityMatrix( m_ViewMatrix );

	RotateAroundAxis(m_ViewMatrix, m_fPitch, 0 );
	RotateAroundAxis(m_ViewMatrix, m_fRoll, 1);
	RotateAroundAxis(m_ViewMatrix, m_fYaw, 2);

	// Translate the viewpoint to the world origin.
	VMatrix TempMatrix;
	TempMatrix.Identity();
	TempMatrix.SetTranslation( -m_ViewPoint );

	m_ViewMatrix = m_ViewMatrix * TempMatrix;

	m_ViewProjMatrix = m_ProjMatrix * m_ViewMatrix;
	m_ViewProjMatrix.InverseGeneral( m_InvViewProjMatrix );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : Matrix - 
//-----------------------------------------------------------------------------
void CCamera::GetViewMatrix(VMatrix& Matrix)
{
	Matrix = m_ViewMatrix;

}

void CCamera::GetProjMatrix(VMatrix& Matrix)
{
	Matrix = m_ProjMatrix;
}


//-----------------------------------------------------------------------------
// Purpose: Sets the view matrix of the current projection
// Output : Matrix - the matrix to store the current projection matrix
//-----------------------------------------------------------------------------
void CCamera::GetViewProjMatrix( VMatrix &Matrix )
{
	Matrix = m_ViewProjMatrix;
}


//-----------------------------------------------------------------------------
// Purpose: to set the zoom in the orthographic gl view
//   Input: fScale - the zoom scale
//-----------------------------------------------------------------------------
void CCamera::SetZoom( float fScale )
{
	if ( m_fZoom != fScale )
	{
	    m_fZoom = fScale;
		BuildProjMatrix();
	}
}


//-----------------------------------------------------------------------------
// Purpose: to accumulate the zoom in the orthographic gl view
//   Input: fScale - the zoom scale
//-----------------------------------------------------------------------------
void CCamera::Zoom( float fScale )
{
    m_fZoom += fScale;

    // don't zoom negative 
    if( m_fZoom < 0.00001f )
        m_fZoom = 0.00001f;
}


//-----------------------------------------------------------------------------
// Purpose: to get the zoom in the orthographic gl view
//  Output: return the zoom scale
//-----------------------------------------------------------------------------
float CCamera::GetZoom( void )
{
    return m_fZoom;
}

void CCamera::WorldToView( const Vector& vWorld, Vector2D &vView )
{
	Vector vView3D;

	Vector3DMultiplyPositionProjective( m_ViewProjMatrix, vWorld, vView3D );

	// NOTE: The negative sign on y is because wc wants to think about screen
	// coordinates in a different way than the material system
	vView.x = 0.5 * (vView3D.x + 1.0) * m_nViewWidth;
	vView.y = 0.5 * (-vView3D.y + 1.0) * m_nViewHeight;
}

void CCamera::ViewToWorld( const Vector2D &vView, Vector& vWorld)
{	
	Vector vView3D;

	vView3D.x = 2.0 * vView.x / m_nViewWidth - 1;
	vView3D.y = -2.0 * vView.y / m_nViewHeight + 1;
	vView3D.z = 0;

	Vector3DMultiplyPositionProjective( m_InvViewProjMatrix, vView3D, vWorld );
}

void CCamera::BuildRay( const Vector2D &vView, Vector& vStart, Vector& vEnd )
{
	// Find the point they clicked on in world coordinates. It lies on the near
	// clipping plane.
	Vector vClickPoint;
	ViewToWorld(vView, vClickPoint );

	// Build a ray from the viewpoint through the point on the near clipping plane.
	Vector vRay = vClickPoint - m_ViewPoint;
	VectorNormalize( vRay );
	
	vStart = m_ViewPoint;
	vEnd = vStart + vRay * 99999;
}