//--------------------------------------------------------------------------------------
// Copyright (C) 2008 http://shift.delblanco.es
//--------------------------------------------------------------------------------------
// SHIFT Videogame is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by the
// Free Software Foundation, either version 3 of the License, or later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
//--------------------------------------------------------------------------------------
// 3DCamera.cpp
//--------------------------------------------------------------------------------------
#include "dxstdafx.h"
#include "3DCamera.h"
#include "3DObjVehicle.h"
#include "Menu.h"

//--------------------------------------------------------------------------------------
// Variables Globales
//--------------------------------------------------------------------------------------
C3DCamera			*g_Camera;         // Instancia para la cámara 3D
bool				g_bCamPer;		   // Flag para indicar si la cámara es o no persecutoria
extern	CMenu       *g_Menu;           // Instancia externa para el menú 3D (Menu.cpp)

//--------------------------------------------------------------------------------------
// Constructor
//--------------------------------------------------------------------------------------
C3DCamera::C3DCamera()
{
    m_bInit = false;
    m_pDevice = NULL;

    m_bModified = false;
	m_bUpdate = false;
}

//--------------------------------------------------------------------------------------
// Destructor
//--------------------------------------------------------------------------------------
C3DCamera::~C3DCamera()
{
    Release();
}

//--------------------------------------------------------------------------------------
// Carga la cámara ante un CreateDevice
//--------------------------------------------------------------------------------------
bool C3DCamera::initialize()
{
    // Salimos si ya se ha inicializado
    if(m_bInit) return false;
    m_bInit = true;

    // Tomamos el dispositivo
    m_pDevice = DXUTGetD3DDevice();
    assert(m_pDevice != NULL);
    m_pDevice->AddRef();

    // Inicializamos los parametros basicos por defecto
    D3DXMatrixIdentity(&m_ViewMatrix);
    D3DXMatrixIdentity(&m_ProjectionMatrix);
    m_To = D3DXVECTOR3(0.0f, 0.0f, 1.0f);
    m_Up = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
    m_Position = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
    m_Rotation = D3DXVECTOR3(0.0f, 0.0f, 0.0f);

    m_fFOV = D3DX_PI / 4.0f;
    m_fZoom = 1.0f;
    m_fAspect = 1.0f;
    m_fNear = 0.1f;
    m_fFar = 1000.0f;
    m_bModified = true;
	m_bUpdate = true;
    m_iType = CAMERA3D_FREE;

    return true;
}

//--------------------------------------------------------------------------------------
// Renderiza la cámara ante un FrameRender
//--------------------------------------------------------------------------------------
void C3DCamera::render()
{
    // Salimos si no se ha inicializado
    if(!m_bInit) return;

    // Establecemos las matrices
    m_pDevice->SetTransform(D3DTS_VIEW, &m_ViewMatrix);
    m_pDevice->SetTransform(D3DTS_PROJECTION, &m_ProjectionMatrix);
}

//--------------------------------------------------------------------------------------
// Actualiza la cámara ante un FrameMove
//--------------------------------------------------------------------------------------
void C3DCamera::update()
{
    // Salimos si no se ha inicializado
    if(!m_bInit) return;

    // Recalculamos las matrices si hubo modificaciones que influyan en ellas
    if(m_bModified) restore();
}

//--------------------------------------------------------------------------------------
// Restaura la cámara ante un ResetDevice
//--------------------------------------------------------------------------------------
void C3DCamera::restore()
{

    // Restauramos dependiendo del tipo de cámara
    switch(m_iType)
    {
    case CAMERA3D_FREE:
        {
            // Obtenemos la orientación frontal y superior a partir de la rotación
            D3DXMATRIX CameraMatrix;
            D3DXMatrixRotationYawPitchRoll(&CameraMatrix, m_Rotation.y, m_Rotation.x, m_Rotation.z);
            m_To = D3DXVECTOR3(0.0f, 0.0f, 1.0f);
            D3DXVec3TransformCoord(&m_To, &m_To, &CameraMatrix);
            m_Up = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
            D3DXVec3TransformCoord(&m_Up, &m_Up, &CameraMatrix);

            // Obtenemos el objetivo de la cámara
            m_To += m_Position;

            // Matriz de vista
            D3DXMatrixLookAtLH(
                &m_ViewMatrix,	// resultado
                &m_Position,	// posicion
                &m_To,			// orientación frontal
                &m_Up);			// orientación superior

            break;
        }
    default:
        {
            // Matriz de vista
            D3DXMatrixLookAtLH(
                &m_ViewMatrix,	// resultado
                &m_Position,	// posicion
                &m_To,	        // orientación frontal
                &m_Up);			// orientación superior

            break;
        }
    }

    // Matriz de proyeccion
    D3DXMatrixPerspectiveFovLH(&m_ProjectionMatrix, m_fFOV/m_fZoom, m_fAspect, m_fNear, m_fFar);

    // Desactivamos el flag, puesto que ya se recalcularon las modificaciones
    m_bModified=false;
}

//--------------------------------------------------------------------------------------
// Libera la cámara ante un LostDevice
//--------------------------------------------------------------------------------------
void C3DCamera::free()
{
    // Salimos si no se ha inicializado
    if(!m_bInit) return;

    // Ajustamos el aspecto de la cámara según las nuevas dimensiones del dispositivo
    D3DPRESENT_PARAMETERS params = DXUTGetPresentParameters();
    if(params.BackBufferHeight != 0)
        setAspect((float)(params.BackBufferWidth)/(float)(params.BackBufferHeight));
}

//--------------------------------------------------------------------------------------
// Libera la camara
//--------------------------------------------------------------------------------------
void C3DCamera::Release()
{
    // Salimos si no se ha inicializado
    if(!m_bInit) return;
    m_bInit = false;

    // Liberamos el dispositivo
    SAFE_RELEASE(m_pDevice);
}


//--------------------------------------------------------------------------------------
// Establece el tipo de cámara
//--------------------------------------------------------------------------------------
void C3DCamera::setType(int Type)
{
	// Guardamos el último tipo de cámara
	m_iLastType = m_iType;

    // Cambiamos el tipo de cámara
    m_iType = Type;
    m_bModified=true;
	m_bUpdate = true;

	// Comprobamos si es persecutoria
    switch(Type)
    {
	case CAMERA3D_GAME_1:
	case CAMERA3D_GAME_2:
	case CAMERA3D_GAME_3:
	case CAMERA3D_GAME_5:
		g_bCamPer = true;
		break;
	case CAMERA3D_USER:
	case CAMERA3D_GAME_4:
	case CAMERA3D_GAME_6:
	case CAMERA3D_GAME_7:
		g_bCamPer = false;
		break;
	}

    // Inicializamos la orientación
    m_To = D3DXVECTOR3(0.0f, 0.0f, 1.0f);
    m_Up = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
    m_Rotation = D3DXVECTOR3(0.0f, 0.0f, 0.0f);
}

//--------------------------------------------------------------------------------------
// Toma el siguiente tipo de cámara
//--------------------------------------------------------------------------------------
void C3DCamera::nextType(void)
{
	int next = m_iType + 1;
	if(next > CAMERA3D_GAME_7) next = CAMERA3D_GAME_1;
	g_Camera->setType(next);
}

//--------------------------------------------------------------------------------------
// Cambia los parámetros de la cámara para perseguir a un punto
// con la posición y velocidad indicadas
//--------------------------------------------------------------------------------------
void C3DCamera::Follow(C3DObjVehicle *Vehicle)
{
	// Salimos si el vehículo a seguir no ha sido modificado
	if (!Vehicle->getLastModified() && !m_bUpdate) return;
	m_bUpdate = false;

	// Necesito determinar estos dos parámetros
	//   - m_Position
	//   - m_To
	// Lo haremos dependiendo del tipo concreto
    switch(m_iType)
    {
		/*
			Cámaras persecutorias
			--------------------------------------------------------------------------------------
		*/
		case CAMERA3D_GAME_1:
		case CAMERA3D_GAME_2:
		case CAMERA3D_GAME_3:
		{
			// Tomamos los parámetros de cada tipo de cámara
			float fixed_height; float factor_height;
			switch(m_iType)
			{
				case CAMERA3D_GAME_1: fixed_height = 40.0f; factor_height = 0.02f; break;
				case CAMERA3D_GAME_2: fixed_height = 60.0f; factor_height = 0.04f; break;
				case CAMERA3D_GAME_3: fixed_height = 80.0f; factor_height = 0.06f; break;
			}

			// Tomamos la posición y velocidad del vehículo
			D3DXVECTOR3 VehiclePosition; Vehicle->getPosition(VehiclePosition);
			float VehicleSpeed = Vehicle->getPhysics()->getLSpeed();

			// Aquí dejaremos la nueva posición de la cámara
			D3DXVECTOR3 NewPosition = m_Position;

			// Calculamos la distancia actual de la cámara al objeto
			NewPosition.y = 0.0f;
			D3DXVECTOR3 DiffPosition = NewPosition - VehiclePosition;
			float Distance = D3DXVec3Length(&DiffPosition);

			// Si la distancia es mayor que la máxima, adecuamos su posición
			if (Distance > (fixed_height * 1.2f))
			{
				float ElapsedTime = DXUTGetElapsedTime();
				D3DXVec3Normalize(&DiffPosition, &DiffPosition);
				DiffPosition = DiffPosition * (Distance - ElapsedTime * 2.0f * (Distance - (fixed_height * 1.2f)));
				NewPosition = VehiclePosition + DiffPosition;
				m_bUpdate = true;
			}

			// Si la distancia es menor que la mínima, adecuamos su posición
			else if (Distance < (fixed_height * 0.6f))
			{
				float ElapsedTime = DXUTGetElapsedTime();
				D3DXVec3Normalize(&DiffPosition, &DiffPosition);
				DiffPosition = DiffPosition * (Distance + ElapsedTime * 8.0f * ((fixed_height * 0.5f) - Distance));
				NewPosition = VehiclePosition + DiffPosition;
				m_bUpdate = true;
			}

			// Altura de la cámara
			NewPosition.y = fixed_height + factor_height * fabs(VehicleSpeed);

			// Actualizamos los parámetros de la cámara
			m_Position = NewPosition;
			m_To = VehiclePosition;
			break;
		}

		/*
			Cámara superior siempre en la misma orientación
			--------------------------------------------------------------------------------------
		*/
		case CAMERA3D_GAME_4:
		{
			// Tomamos la matriz de transformación del vehículo
			D3DXMATRIX *trans_1 = Vehicle->getTransformation();
			D3DXVECTOR4 NewPosition4 (0.0f, 500.0f, 0.0f, 1.0f);
			D3DXVec4Transform(&NewPosition4, &NewPosition4, trans_1);
			D3DXVECTOR3 NewPosition (NewPosition4.x + 0.1f, NewPosition4.y, NewPosition4.z);
			D3DXVECTOR3 NewTo (NewPosition4.x, 0.0f, NewPosition4.z);

			// Actualizamos los parámetros de la cámara
			m_Position = NewPosition;
			m_To = NewTo;
			break;
		}

		/*
			Cámara superior siempre en la misma orientación y posición
			--------------------------------------------------------------------------------------
		*/
		case CAMERA3D_GAME_5:
		{
			// Actualizamos los parámetros de la cámara
			m_Position = D3DXVECTOR3 (0.0f, g_Menu->getMGame()->getArena()->getSize() * 2.7f, 0.0f);
			m_To = D3DXVECTOR3 (-0.1f, 0.0f, 0.0f);
			break;
		}

		/*
			Cámara interior encima del capó
			--------------------------------------------------------------------------------------
		*/
		case CAMERA3D_GAME_6:
		{
			// Tomamos si miramos hacia atrás o adelante
			bool Back = false;

			// Tomamos la matriz de transformación del vehículo
			D3DXMATRIX *trans_1 = Vehicle->getTransformation();
			D3DXVECTOR3 *dir = Vehicle->getPhysics()->getLDirection();
			float cameraY = Vehicle->getCameraY();
			// Hayamos el FROM de la cámara en función de la velocidad
			D3DXVECTOR4 NewPosition4;
			if (Back) NewPosition4 = D3DXVECTOR4(-4.0f, cameraY, 0.0f, 1.0f);
			else NewPosition4 = D3DXVECTOR4(-4.0f, cameraY, 0.0f, 1.0f);
			D3DXVec4Transform(&NewPosition4, &NewPosition4, trans_1);
			D3DXVECTOR3 NewPosition (NewPosition4.x, NewPosition4.y, NewPosition4.z);
			// Hayamos el TO de la cámara en función de la velocidad
			D3DXVECTOR3 NewTo;
			if (Back) NewTo = D3DXVECTOR3(NewPosition4.x - dir->x, NewPosition4.y, NewPosition4.z - dir->z);
			else NewTo = D3DXVECTOR3(NewPosition4.x + dir->x, NewPosition4.y, NewPosition4.z + dir->z);

			// Actualizamos los parámetros de la cámara
			m_Position = NewPosition;
			m_To = NewTo;
			break;
		}

		/*
			Cámara superior al vehículo desde un punto fijo
			--------------------------------------------------------------------------------------
		*/
		case CAMERA3D_GAME_7:
		{
			// Tomamos la posición del vehículo
			D3DXVECTOR3 VehiclePosition; Vehicle->getPosition(VehiclePosition);

			// Actualizamos los parámetros de la cámara
			m_Position.x = 120.0f;
			m_Position.y = 200.0f;
			m_Position.z = 120.0f;
			m_To = VehiclePosition;
			break;
		}

    }

	// La cámara ha sido modificada
    m_bModified = true;
}
