// Camera.cpp: implementation of the CCamera class.
//
//////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "../eterBase/Utils.h"
#include "Camera.h"

const float c_fDefaultResistance = 0.3f;

CCameraManager aCameraManager;	// CCameraManager Instance

void CCamera::SetCameraMaxDistance(float fMax)
{
	CAMERA_MAX_DISTANCE = fMax;
}

float CCamera::GetTargetHeight()
{
	return m_fTarget_;
}

void CCamera::SetTargetHeight(float fTarget)
{
	m_fTarget_=fTarget;	
}


//////////////////////////////////////////////////////////////////////////
// CCamera
//////////////////////////////////////////////////////////////////////////


CCamera::CCamera() :
m_fEyeGroundHeightRatio(0.3f),
m_fTargetHeightLimitRatio(2.0f),
m_fResistance(c_fDefaultResistance),
m_isLock(false)
{
	m_fDistance						= 1.0f;
	m_eCameraState					= CAMERA_STATE_NORMAL;
	m_eCameraStatePrev				= CAMERA_STATE_NORMAL;
	m_ulNumScreenBuilding			= 0;

	m_fPitchSum = 0.0f;
	m_fRollSum = 0.0f;

	m_fTerrainCollisionRadius		= 50.0f;
	m_fObjectCollisionRadius		= 50.0f;

	m_bDrag							= false;

	m_lMousePosX					= -1;
	m_lMousePosY					= -1;

	m_fTarget_						= CAMERA_TARGET_STANDARD;

	m_v3AngularAcceleration			= D3DXVECTOR3(0.0f, 0.0f, 0.0f);
	m_v3AngularVelocity				= D3DXVECTOR3(0.0f, 0.0f, 0.0f);

	m_bProcessTerrainCollision		= true;

    SetViewParams(D3DXVECTOR3(0.0f,0.0f,1.0f), D3DXVECTOR3(0.0f,0.0f,0.0f), D3DXVECTOR3(0.0f,1.0f,0.0f));
}

CCamera::~CCamera()
{
}

void CCamera::Lock()
{
	m_isLock = true;
}

void CCamera::Unlock()
{
	m_isLock = false;
}

bool CCamera::IsLock()
{
	return m_isLock;
}

void CCamera::SetResistance(float fResistance)
{
	m_fResistance = c_fDefaultResistance * fResistance;
}

void CCamera::Wheel(int nLen)
{
	if (IsLock())
		return;

	m_v3AngularVelocity.y = (float)(nLen) * m_fResistance;
}

void CCamera::BeginDrag(int nMouseX, int nMouseY)
{
	if (IsLock())
		return;

	m_bDrag = true;
	m_lMousePosX = nMouseX;
	m_lMousePosY = nMouseY;
	m_fPitchSum = 0.0f;
	m_fRollSum = 0.0f;
}

bool CCamera::IsDraging()
{
	if (IsLock())
		return false;

	return m_bDrag;
}

bool CCamera::EndDrag()
{
	if (IsLock())
		return false;

	m_bDrag = false;

	float fSum=sqrt(m_fPitchSum*m_fPitchSum+m_fRollSum*m_fRollSum);

	m_fPitchSum = 0.0f;
	m_fRollSum = 0.0f;
	
	if (fSum<1.0f)
		return false;

	return true;
}

bool CCamera::Drag(int nMouseX, int nMouseY, LPPOINT lpReturnPoint)
{
	if (IsLock())
		return false;

	if (!m_bDrag)
	{
		m_lMousePosX = nMouseX;
		m_lMousePosY = nMouseY;
		lpReturnPoint->x = m_lMousePosX;
		lpReturnPoint->y = m_lMousePosY;
		return false;
	}
	
	long lMouseX = nMouseX;
	long lMouseY = nMouseY;
	
	float fNewPitchVelocity = (float)(lMouseY - m_lMousePosY) * m_fResistance;
	float fNewRotationVelocity = (float)(lMouseX - m_lMousePosX) * m_fResistance;

	m_fPitchSum += fNewPitchVelocity;
	m_fRollSum += fNewRotationVelocity;
	
	
	if (CAMERA_STATE_CANTGOLEFT == GetCameraState())
		fNewRotationVelocity = fMAX(0.0f, fNewRotationVelocity);
	if (CAMERA_STATE_CANTGORIGHT == GetCameraState())
		fNewRotationVelocity = fMIN(0.0f, fNewRotationVelocity);
	if (CAMERA_STATE_CANTGODOWN == GetCameraState())
		fNewPitchVelocity = fMAX(0.0f, fNewPitchVelocity);
	
	m_v3AngularVelocity.x = fNewRotationVelocity;
	m_v3AngularVelocity.z = fNewPitchVelocity;

	lpReturnPoint->x = m_lMousePosX;
	lpReturnPoint->y = m_lMousePosY;
	return true;
}

//////////////////////////////////////////////////////////////////////////
// Update

void CCamera::SetCameraState(eCameraState eNewCameraState)
{
	if (eNewCameraState == m_eCameraState)
		return;

	m_eCameraStatePrev = m_eCameraState;
	m_eCameraState = eNewCameraState;

/*
	if ((CAMERA_STATE_NORMAL == m_eCameraStatePrev))
	{
		m_fDistanceBackup = m_fDistance;
		m_fPitchBackup = m_fPitch;
		m_fRollBackup = m_fRoll;
	}
	else if ((CAMERA_STATE_CANTGODOWN == m_eCameraStatePrev) && (CAMERA_STATE_CANTGODOWN == m_eCameraState) )
	{
		m_v3EyeBackup = m_v3Eye;
	}
*/
}

void CCamera::IncreaseNumSrcreenBuilding()
{
	++m_ulNumScreenBuilding;
}

void CCamera::ResetNumScreenBuilding()
{
	m_ulNumScreenBuilding = 0;
}

//////////////////////////////////////////////////////////////////////////
// Property
void CCamera::SetViewParams( const D3DXVECTOR3 &v3Eye, const D3DXVECTOR3& v3Target, const D3DXVECTOR3& v3Up)
{
	if (IsLock())
		return;

    // Set attributes for the view matrix
    m_v3Eye = v3Eye;
    m_v3Target = v3Target;
    m_v3Up = v3Up;

	SetViewMatrix();
}

void CCamera::SetEye(const D3DXVECTOR3 & v3Eye)
{
	if (IsLock())
		return;

    m_v3Eye = v3Eye;

	SetViewMatrix();
}

void CCamera::SetTarget(const D3DXVECTOR3 & v3Target)
{
	if (IsLock())
		return;

    m_v3Target = v3Target;

	SetViewMatrix();
}

void CCamera::SetUp(const D3DXVECTOR3 & v3Up)
{
	if (IsLock())
		return;

    m_v3Up = v3Up;

	SetViewMatrix();
}

void CCamera::SetViewMatrix()
{
	m_v3View = m_v3Target - m_v3Eye;
	D3DXVECTOR3 v3CenterRay = -m_v3View;
 	CalculateRoll();
	m_fDistance = D3DXVec3Length(&m_v3View);
	assert(m_fDistance >= 0);
	D3DXVec3Normalize(&m_v3View , &m_v3View);

    D3DXVec3Cross(&m_v3Cross, &m_v3Up, &m_v3View);
	D3DXVec3Normalize(&m_v3Cross, &m_v3Cross);

    D3DXVec3Cross(&m_v3Up, &m_v3View, &m_v3Cross);
	D3DXVec3Normalize(&m_v3Up, &m_v3Up);
	const auto vv = D3DXVECTOR3(0.0f, 0.0f, 1.0f);
	m_fPitch = D3DXVec3Dot(&m_v3Up, &vv);// / D3DXVec2Length(&v2ViewYZ);
	if (m_fPitch >= 1)
		m_fPitch = 1;
	else if (m_fPitch <= -1)
		m_fPitch = -1;
	m_fPitch = acosf(m_fPitch);
	m_fPitch *= (180.0f / D3DX_PI);
	if ( 0 < m_v3View.z )
		m_fPitch = -m_fPitch;

	D3DXMatrixLookAtRH(&m_matView, &m_v3Eye, &m_v3Target, &m_v3Up);

	float fDeterminantD3DMatView = D3DXMatrixfDeterminant(&m_matView);
    D3DXMatrixInverse(&m_matInverseView, &fDeterminantD3DMatView, &m_matView);

	m_matBillboard = m_matInverseView;
    m_matBillboard._41 = 0.0f;
    m_matBillboard._42 = 0.0f;
    m_matBillboard._43 = 0.0f;

	m_ViewRay.SetStartPoint(m_v3Target);
	m_ViewRay.SetDirection(v3CenterRay, m_fDistance);

	m_kCameraBottomToTerrainRay.SetStartPoint(m_v3Eye);
	m_kCameraFrontToTerrainRay.SetStartPoint(m_v3Eye);
	m_kCameraBackToTerrainRay.SetStartPoint(m_v3Eye);
	m_kCameraLeftToTerrainRay.SetStartPoint(m_v3Eye);
	m_kCameraRightToTerrainRay.SetStartPoint(m_v3Eye);
	m_kTargetToCameraBottomRay.SetStartPoint(m_v3Target);

	m_kCameraBottomToTerrainRay.SetDirection(-m_v3Up, 2.0f * m_fTerrainCollisionRadius);
	m_kCameraFrontToTerrainRay.SetDirection(m_v3View, 4.0f * m_fTerrainCollisionRadius);
	m_kCameraBackToTerrainRay.SetDirection(-m_v3View, m_fTerrainCollisionRadius);
	m_kCameraLeftToTerrainRay.SetDirection(-m_v3Cross, 3.0f * m_fTerrainCollisionRadius);
	m_kCameraRightToTerrainRay.SetDirection(m_v3Cross, 3.0f * m_fTerrainCollisionRadius);
	const auto vv2 = (v3CenterRay - m_fTerrainCollisionRadius * m_v3Up);
	m_kTargetToCameraBottomRay.SetDirection(v3CenterRay - m_fTerrainCollisionRadius * m_v3Up, D3DXVec3Length(&vv2));

	m_kLeftObjectCollisionRay.SetStartPoint(m_v3Target);
	m_kTopObjectCollisionRay.SetStartPoint(m_v3Target);
	m_kRightObjectCollisionRay.SetStartPoint(m_v3Target);
	m_kBottomObjectCollisionRay.SetStartPoint(m_v3Target);
	const auto vv3 = (v3CenterRay + m_fObjectCollisionRadius * m_v3Cross);
	const auto vv4 = (v3CenterRay - m_fObjectCollisionRadius * m_v3Cross);
	const auto vv5 = (v3CenterRay + m_fObjectCollisionRadius * m_v3Up);
	const auto vv6 = (v3CenterRay + m_fObjectCollisionRadius * m_v3Up);
	m_kLeftObjectCollisionRay.SetDirection(v3CenterRay + m_fObjectCollisionRadius * m_v3Cross, D3DXVec3Length(&vv3));
	m_kRightObjectCollisionRay.SetDirection(v3CenterRay - m_fObjectCollisionRadius * m_v3Cross, D3DXVec3Length(&vv4));
	m_kTopObjectCollisionRay.SetDirection(v3CenterRay + m_fObjectCollisionRadius * m_v3Up, D3DXVec3Length(&vv5));
	m_kBottomObjectCollisionRay.SetDirection(v3CenterRay - m_fObjectCollisionRadius * m_v3Up, D3DXVec3Length(&vv6));
}

void CCamera::Move(const D3DXVECTOR3 & v3Displacement)
{
	if (IsLock())
		return;

	m_v3Eye += v3Displacement;
	m_v3Target += v3Displacement; 

	SetViewMatrix();
}

void CCamera::Zoom(float fRatio)
{
	if (IsLock())
		return;

	if (fRatio == 1.0f)
		return;

	D3DXVECTOR3 v3Temp = m_v3Eye - m_v3Target;
	v3Temp *= fRatio;
	m_v3Eye = v3Temp + m_v3Target;

	SetViewMatrix();
}

void CCamera::MoveAlongView(float fDistance) 
{
	if (IsLock())
		return;

	D3DXVECTOR3 v3Temp;
	D3DXVec3Normalize(&v3Temp, &m_v3View);
	
	m_v3Eye += v3Temp * fDistance;
	m_v3Target += v3Temp * fDistance;
	
	SetViewMatrix();
}

void CCamera::MoveAlongCross(float fDistance) 
{
	if (IsLock())
		return;

	D3DXVECTOR3 v3Temp;
	D3DXVec3Normalize(&v3Temp, &m_v3Cross);

	m_v3Eye += v3Temp * fDistance;
	m_v3Target += v3Temp * fDistance;

	SetViewMatrix();
}

void CCamera::MoveAlongUp(FLOAT fDistance) 
{
	if (IsLock())
		return;

	D3DXVECTOR3 v3Temp ;
	D3DXVec3Normalize(&v3Temp, &m_v3Up);
	m_v3Target += v3Temp * fDistance;
	m_v3Eye += v3Temp * fDistance;
	SetViewMatrix();
}

void CCamera::MoveLateral(float fDistance)
{
	if (IsLock())
		return;

	MoveAlongCross(fDistance);
}

void CCamera::MoveFront(float fDistance) 
{	
	if (IsLock())
		return;

	D3DXVECTOR3 v3Temp = D3DXVECTOR3(m_v3View.x, m_v3View.y, 0.0f);
	D3DXVec3Normalize(&v3Temp, &v3Temp);

	m_v3Eye += v3Temp * fDistance; 
	m_v3Target += v3Temp * fDistance;
	
	SetViewMatrix();	
}

void CCamera::MoveVertical(float fDistance) 
{
	if (IsLock())
		return;

	m_v3Eye.z += fDistance;
	m_v3Target.z += fDistance;

	SetViewMatrix();
}

//void CCamera::RotateUpper(float fDegree)
//{
//	D3DXMATRIX matRot;
//	D3DXMatrixRotationAxis(&matRot, &m_v3Cross, -D3DXToRadian(fDegree));
//	D3DXVec3TransformCoord(&m_v3View, &m_v3View, &matRot) ;
//    D3DXVec3Cross(&m_v3Up, &m_v3View, &m_v3Cross);
//
//	m_v3Target = m_v3Eye + m_v3View;
//
//	SetViewMatrix() ;
//}

void CCamera::RotateEyeAroundTarget(float fPitchDegree, float fRollDegree) 
{
	if (IsLock())
		return;

	D3DXMATRIX matRot, matRotPitch, matRotRoll;

	// 머리위로 넘어가기 막기...
	if (m_fPitch + fPitchDegree > 80.0f)
	{
		fPitchDegree = 80.0f - m_fPitch;
	}
	else if( m_fPitch + fPitchDegree < -80.0f)
	{
		fPitchDegree = -80.0f - m_fPitch;
	}

	D3DXMatrixRotationAxis(&matRotPitch, &m_v3Cross, D3DXToRadian(fPitchDegree));

	D3DXMatrixRotationZ(&matRotRoll, -D3DXToRadian(fRollDegree));
	matRot = matRotPitch * matRotRoll;

	D3DXVECTOR3 v3Temp = m_v3Eye - m_v3Target;
	D3DXVec3TransformCoord(&m_v3Eye, &v3Temp, &matRot);
	m_v3Eye += m_v3Target;

	SetUp(D3DXVECTOR3(0.0f, 0.0f, 1.0f));

	m_fRoll += fRollDegree;

	if (m_fRoll > 360.0f)
		m_fRoll -= 360.0f;
	else if (m_fRoll < -360.0f)
		m_fRoll += 360.0f;
}

void CCamera::RotateEyeAroundPoint(const D3DXVECTOR3 & v3Point, float fPitchDegree, float fRollDegree)
{
//	if (IsLock())
//		return;

	D3DXMATRIX matRot, matRotPitch, matRotRoll;

	D3DXMatrixRotationAxis(&matRotPitch, &m_v3Cross, D3DXToRadian(fPitchDegree));

	D3DXMatrixRotationZ(&matRotRoll, -D3DXToRadian(fRollDegree));
	matRot = matRotPitch * matRotRoll;
	
	D3DXVECTOR3 v3Temp = m_v3Eye - v3Point;
	D3DXVec3TransformCoord(&m_v3Eye, &v3Temp, &matRot);
	m_v3Eye += v3Point;
	
	const auto vv2 = (v3Temp + m_v3Up);
	D3DXVec3TransformCoord(&m_v3Up, &vv2, &matRot);
	m_v3Up -= (m_v3Eye - v3Point);
	
	v3Temp = m_v3Target - v3Point;
	D3DXVec3TransformCoord(&m_v3Target, &v3Temp, &matRot);
	m_v3Target += v3Point;

	SetViewMatrix();
}

void CCamera::Pitch(const float fPitchDelta)
{
//	if (IsLock())
//		return;

	RotateEyeAroundTarget(fPitchDelta, 0.0f);
}

void CCamera::Roll(const float fRollDelta)
{
//	if (IsLock())
//		return;

	RotateEyeAroundTarget(0.0f, fRollDelta);
}

void CCamera::SetDistance(const float fdistance)
{
//	if (IsLock())
//		return;

	Zoom(fdistance/m_fDistance);
}

void CCamera::CalculateRoll()
{
	D3DXVECTOR2 v2ViewXY;
	v2ViewXY.x = m_v3View.x;
	v2ViewXY.y = m_v3View.y;
 	D3DXVec2Normalize(&v2ViewXY, &v2ViewXY);
	const auto vv = D3DXVECTOR2(0.0f, 1.0f);
	float fDot = D3DXVec2Dot(&v2ViewXY, &vv);
	if (fDot >= 1)
		fDot = 1;
	else if (fDot <= -1)
		fDot = -1;
	fDot = acosf(fDot);
	fDot *= (180.0f / D3DX_PI);
	float fCross = D3DXVec2CCW (&v2ViewXY, &vv);
	if ( 0 > fCross)
	{
		fDot = -fDot;
	}

	m_fRoll = fDot;

}

//////////////////////////////////////////////////////////////////////////
// CCameraMananger
//////////////////////////////////////////////////////////////////////////

CCameraManager::CCameraManager() :
m_pCurrentCamera(NULL),
m_pPreviousCamera(NULL)
{
	AddCamera(DEFAULT_PERSPECTIVE_CAMERA);
	AddCamera(DEFAULT_ORTHO_CAMERA);

	SetCurrentCamera(DEFAULT_PERSPECTIVE_CAMERA);
}

CCameraManager::~CCameraManager()
{
	for (TCameraMap::iterator itor = m_CameraMap.begin(); itor != m_CameraMap.end(); ++itor)
	{
		delete (*itor).second;
	}
	m_CameraMap.clear();
}

CCamera * CCameraManager::GetCurrentCamera()
{
	if (!m_pCurrentCamera)
		assert(false);
	return m_pCurrentCamera;
}

void CCameraManager::SetCurrentCamera(unsigned char ucCameraNum)
{
	if (m_pCurrentCamera != m_CameraMap[ucCameraNum])
		m_pPreviousCamera = m_pCurrentCamera;

	m_pCurrentCamera = m_CameraMap[ucCameraNum];
}

void CCameraManager::ResetToPreviousCamera()
{
	if (!m_pPreviousCamera)
		assert(false);
	m_pCurrentCamera = m_pPreviousCamera;
	m_pPreviousCamera = NULL;
}

bool CCameraManager::isCurrentCamera(unsigned char ucCameraNum)
{
	if (m_CameraMap[ucCameraNum] == m_pCurrentCamera)
		return true;
	return false;
}

// 잡스러운 함수들...
bool CCameraManager::AddCamera(unsigned char ucCameraNum)
{
	if(m_CameraMap.end() != m_CameraMap.find(ucCameraNum))
		return false;
	m_CameraMap.insert(TCameraMap::value_type(ucCameraNum, new CCamera));
	return true;
}

bool CCameraManager::RemoveCamera(unsigned char ucCameraNum)
{
	TCameraMap::iterator itor = m_CameraMap.find(ucCameraNum);
	if(m_CameraMap.end() == itor)
		return false;
	m_CameraMap.erase(itor);
	return true;
}

unsigned char CCameraManager::GetCurrentCameraNum()
{
	if (!m_pCurrentCamera)
		return NO_CURRENT_CAMERA;
	for (TCameraMap::iterator itor = m_CameraMap.begin(); itor != m_CameraMap.end(); ++itor)
		if(m_pCurrentCamera == (*itor).second)
			return (*itor).first;
	return NO_CURRENT_CAMERA;
}

bool CCameraManager::isTerrainCollisionEnable()
{
	return m_pCurrentCamera->isTerrainCollisionEnable();
}

void CCameraManager::SetTerrainCollision(bool bEnable)
{
	m_pCurrentCamera->SetTerrainCollision(bEnable);
}
