////////////////////////////////////////////////////////////////////////////
//  Camera.cpp                                                            //
//                                                                        //
//  Michael Silverman, 2011                                               //
//     Dstributed under the Boost Software Licence, Version 1.0.          //
//     http://www.boost.org/LICENSE_1_0.txt                               //
//     http://silverwaregames.com/LICENSE_1_0.txt                         //
//                                                                        //
////////////////////////////////////////////////////////////////////////////

#include "Camera.h"

Camera CAMERA; // singleton definition

Camera::Camera( bool bFilter /*= true */ )
{
    m_fFilterAlpha = .1f;
    m_bFilter = bFilter;

    m_v3Position = m_v3FilteredPosition = D3DXVECTOR3( 0, 0, 0 );

    D3DXQuaternionIdentity( &m_qRotation );
    m_qFilteredRotation = m_qRotation;
}

void Camera::RotYaw( float fTheta )
{
    D3DXVECTOR3 v3Up( 0.0f, 1.0f, 0.0f );
    D3DXQUATERNION qRot;

    D3DXQuaternionRotationAxis( &qRot, &v3Up, -fTheta );
    D3DXQuaternionMultiply( &m_qRotation, &qRot, &m_qRotation );
}

void Camera::RotPitch( float fTheta )
{
    D3DXVECTOR3 v3Right( 1.0f, 0.0f, 0.0f );
    D3DXQUATERNION qRot;

    D3DXQuaternionRotationAxis( &qRot, &v3Right, fTheta );
    D3DXQuaternionMultiply( &m_qRotation, &m_qRotation, &qRot );
}

void Camera::SetYawPitchRoll( float fYaw, float fPitch, float fRoll )
{
    D3DXQuaternionRotationYawPitchRoll( &m_qRotation, fYaw, fPitch, fRoll );
}

void Camera::SetAxis( const D3DXVECTOR3 &v3LookAt, const D3DXVECTOR3 &v3Up, const D3DXVECTOR3 &v3Right )
{
    D3DXMATRIX mRotation;
    D3DXMatrixIdentity( &mRotation );

    mRotation(0,0) = v3Right.x;    
    mRotation(0,1) = v3Up.x;
    mRotation(0,2) = v3LookAt.x;

    mRotation(1,0) = v3Right.y;
    mRotation(1,1) = v3Up.y;
    mRotation(1,2) = v3LookAt.y;

    mRotation(2,0) = v3Right.z;
    mRotation(2,1) = v3Up.z;
    mRotation(2,2) = v3LookAt.z;

    D3DXQuaternionRotationMatrix( &m_qRotation, &mRotation );
}

void Camera::SetRotation( const D3DXQUATERNION &qRotation )
{
    m_qRotation = qRotation;
}

void Camera::SetFocalPoint( const D3DXVECTOR3 &v3FocalPoint, const D3DXVECTOR3 &v3Up /*= D3DXVECTOR3(0,1,0) */ )
{
    SetFocalPointAndPosition( v3FocalPoint, m_v3Position, v3Up );
}

void Camera::SetFocalPointAndPosition( const D3DXVECTOR3 &v3FocalPoint, 
                                      const D3DXVECTOR3 &v3Position, 
                                      const D3DXVECTOR3 &v3UpVec /*= D3DXVECTOR3(0,1,0) */ )
{
    m_v3Position = v3Position;

    D3DXVECTOR3 v3Up, v3Right, v3LookAt = v3FocalPoint - m_v3Position;

    D3DXVec3Normalize( &v3LookAt, &v3LookAt );
    D3DXVec3Cross( &v3Right, &v3UpVec, &v3LookAt );
    D3DXVec3Normalize( &v3Right, &v3Right );
    D3DXVec3Cross( &v3Up, &v3LookAt, &v3Right );
    D3DXVec3Normalize( &v3Up, &v3Up );

    SetAxis( v3LookAt, v3Up, v3Right );
}

void Camera::SetPosition( const D3DXVECTOR3 &v3Position )
{
    m_v3Position = v3Position;
}

void Camera::MoveCamera( const D3DXVECTOR3 &v3Delta )
{
    m_v3Position += v3Delta; 
}

D3DXVECTOR3 Camera::GetPosition( )
{
    return m_v3Position;
}

D3DXVECTOR3 Camera::GetLookAt( )
{
    D3DXMATRIX mRot;
    D3DXMatrixRotationQuaternion( &mRot, &m_qRotation );
    return D3DXVECTOR3( mRot(0,2), mRot(1,2), mRot(2,2) );
}

D3DXVECTOR3 Camera::GetRight( )
{
    D3DXMATRIX mRot;
    D3DXMatrixRotationQuaternion( &mRot, &m_qRotation );
    return D3DXVECTOR3( mRot(0,0), mRot(1,0), mRot(2,0) );
}

D3DXVECTOR3 Camera::GetUp( )
{
    D3DXMATRIX mRot;
    D3DXMatrixRotationQuaternion( &mRot, &m_qRotation );
    return D3DXVECTOR3( mRot(0,1), mRot(1,1), mRot(2,1) );
}

D3DXVECTOR3 Camera::GetRayCastDirection( )
{
    float fXPercent = GetMouseX( ) / (float)GetWindowWidth( );
    float fYPercent = GetMouseY( ) / (float)GetWindowHeight( );
    fXPercent -= .5f;
    fYPercent -= .5f;
    fXPercent *= 2.0f;
    fYPercent *= 2.0f;

    float fScreenAspect = (float)GetWindowWidth( ) / (float)GetWindowHeight( );
    float fTanFOV = tan( ( D3DX_PI / 4.0f ) / 2.0f );
    float fRightLength = fTanFOV * fXPercent * fScreenAspect;
    float fUpLength =   -fTanFOV * fYPercent;

    D3DXVECTOR3 v3LookAt, v3Up, v3Right;
    v3LookAt = GetLookAt( );
    v3Up = GetUp( );
    v3Right = GetRight( );

    D3DXVECTOR3 v3Result = v3LookAt + fRightLength * v3Right  + fUpLength * v3Up;
    D3DXVec3Normalize( &v3Result, &v3Result );
    return v3Result;
}

D3DXMATRIX Camera::GetViewMatrix( )
{
    D3DXMATRIX mViewMatrix, mTranslation;
    D3DXVECTOR3 v3Position;
    D3DXQUATERNION qRotation;

    D3DXMatrixIdentity( &mViewMatrix );
    D3DXMatrixIdentity( &mTranslation );

    if( m_bFilter )
    {
        v3Position = m_v3FilteredPosition;
        qRotation = m_qFilteredRotation;
    }
    else
    {
        v3Position = m_v3Position;
        qRotation = m_qRotation;
    }

    D3DXMatrixRotationQuaternion( &mViewMatrix, &qRotation );
    D3DXMatrixTranslation( &mTranslation, -v3Position.x, -v3Position.y, -v3Position.z );
    D3DXMatrixMultiply( &mViewMatrix, &mTranslation, &mViewMatrix );

    return mViewMatrix;
}

void Camera::PrepareForRender()
{
    if( GetRenderDevice( ) )
    {
        GetRenderDevice( )->SetTransform( D3DTS_VIEW, &GetViewMatrix( ) );
        // TODO: You may want to pass the view matrix to shaders and other effectx.
    }
}

void Camera::Init( )
{
    SetFocalPointAndPosition( D3DXVECTOR3( 0, 0.0f, 0.0 ),
                              D3DXVECTOR3( 0.0f, 0.0f, -20.0f ) );
    if( m_bFilter )
    {
        m_qFilteredRotation = m_qRotation; 
        m_v3FilteredPosition = m_v3Position; 
    };
}

void Camera::Update()
{
    if( IsKeyDown('A') )
    {
        D3DXVECTOR3 v3Move = -GetRight();
        v3Move *= GetSecFromLastFrame( ) * 10.0f;
        MoveCamera( v3Move );
    }

    if( IsKeyDown('D') )
    {
        D3DXVECTOR3 v3Move = GetRight();
        v3Move *= GetSecFromLastFrame( ) * 10.0f;
        MoveCamera( v3Move );
    }

    if( IsKeyDown('R') )
    {
        D3DXVECTOR3 v3Move = GetUp();
        v3Move *= GetSecFromLastFrame( ) * 10.0f;
        MoveCamera( v3Move );
    }

    if( IsKeyDown('F') )
    {
        D3DXVECTOR3 v3Move = -GetUp();
        v3Move *= GetSecFromLastFrame( ) * 10.0f;
        MoveCamera( v3Move );
    }

    if( IsKeyDown('W') )
    {
        D3DXVECTOR3 v3Move = GetLookAt();
        v3Move *= GetSecFromLastFrame( ) * 10.0f;
        MoveCamera( v3Move );
    }

    if( IsKeyDown('S') )
    {
        D3DXVECTOR3 v3Move = -GetLookAt();
        v3Move *= GetSecFromLastFrame( ) * 10.0f;
        MoveCamera( v3Move );
    }

    if( IsKeyDown(VK_LEFT) )
    {
        RotYaw( -GetSecFromLastFrame( ) );
    }

    if( IsKeyDown(VK_RIGHT) )
    {
        RotYaw( GetSecFromLastFrame( ) );
    }

    if( IsKeyDown(VK_UP) )
    {
        RotPitch( GetSecFromLastFrame( ) );
    }

    if( IsKeyDown(VK_DOWN) )
    {
        RotPitch( -GetSecFromLastFrame( ) );
    }
    
    // TODO: Implement your own camera logic here. The camera is currently set up as a WSAD movement with arrow keys as rotation.

    if( m_bFilter )
    {
        // Compute exponential moving average and store it in the filtered members.
        D3DXVec3Lerp( &m_v3FilteredPosition, &m_v3FilteredPosition, &m_v3Position, m_fFilterAlpha );
        D3DXQuaternionSlerp( &m_qFilteredRotation, &m_qFilteredRotation, &m_qRotation, m_fFilterAlpha );
    }
}

void Camera::SetFiltering( bool bFilter )
{
    m_bFilter = bFilter; 
    if( m_bFilter )
    {
        m_qFilteredRotation = m_qRotation; 
        m_v3FilteredPosition = m_v3Position; 
    };
}