/*****************************************************************************
 * $LastChangedDate: 2010-09-25 18:43:57 -0400 (Sat, 25 Sep 2010) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Functor attached to a View that computes view matrix for chase-plane view.
 *//*
 * LEGAL:   COPYRIGHT (C) 2008 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#define VIEW_VIEWPOINT_CHASE_CC 1
#include "base/module.hh"
using namespace base;
#include "math/module.hh"
#include "math/funcs_trig.hh"
#include "math/matrix.hh"
#include "math/matrix_funcs.hh"
using namespace math;
#include "world/module.hh"
#include "world/conf.hh"
using namespace world;
#include "view/module.hh"
#include "view/viewpoint.hh"
#include "view/viewpoint_chase.hh"
#include "view/view.hh"

namespace view {

const Meter DEFAULT_CHASE_PLANE_DISTANCE( 20.0f );

////////////////////////////////////////////////////////////////////////////////
/////////////////////////////  ChaseViewpoint  /////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
ChaseViewpoint::ChaseViewpoint( void )
{
    SET_TYPESIG(this,TYPESIG_CHASE_VIEWPOINT);

    // Set initial distance of chase-plane.
    // Chase-plane is initially behind (along -Z axis in local space).
    SetDistance( WorldVertex( 0.0f, 0.0f, world::conv::Meters2Sim(DEFAULT_CHASE_PLANE_DISTANCE) ) );

    // No need to listen for the event of target moving.
    // Rather, every frame, the graphics loop invokes this anyway.
}

ChaseViewpoint::~ChaseViewpoint()
{
    INVALIDATE_TYPESIG(this,TYPESIG_CHASE_VIEWPOINT);
}

/*****************************************************************************
 * This is called every frame by Window's graphics loop.
 * Viewpoint must compute the view matrix for the current mode of its View.
 *****************************************************************************/
Matrix
ChaseViewpoint::ComputeViewMatrix( shptr<View> view )
{
CHECK_TYPESIG(this,TYPESIG_CHASE_VIEWPOINT);
CHECK_TYPESIG(view,TYPESIG_VIEW);

    // Compute a "look at" matrix for chase-plane mode.

    // Compute the "target" vector.
    WorldVertex target = mState.mTarget->GetPosition();
  //target = target * tangentMatrix;  // N/A

    // Compute the "up" vector.
    // "tangent matrix" is a matrix aligned to surface of world.
#if COMPILE_PALOMINO
    const Matrix& tangentMatrix = GET_WORLD().GetTangentMatrix( world::conv::WorldVertex2SphereVertex(target) );
    Vector3 up( 0.0f, 0.0f, 1.0f );
    up = up * tangentMatrix;
#else
    const Vector3 up = GET_WORLD().ComputeUpVector( *mState.mTarget );
#endif

    // Compute the "viewpoint" vector.
    Vector3 distance = WorldVertex( mState.mDistance );
#if COMPILE_PALOMINO
    distance = distance * tangentMatrix;
#endif
    Vector3 viewpoint = target + distance;

    // Compute the "lookat" matrix.
    Matrix matrix;
    matrix.makeLookAt( viewpoint,   // gluLookAt: eye
                       target,      // gluLookAt: center
                       up );        // gluLookAt: up
    return matrix;
}

/*****************************************************************************
 * Save state to be restored by ChasePlaneReset().
 *****************************************************************************/
void
ChaseViewpoint::Reset( void )
{
CHECK_TYPESIG(this,TYPESIG_CHASE_VIEWPOINT);

    // Keep the same target!
    // Solves bug of chase-plane following a replaced target.
    shptr<Object> target = mState.mTarget;
    mState = mSavedState;
    mState.mTarget = target;
}

void
ChaseViewpoint::Save( void )
{
CHECK_TYPESIG(this,TYPESIG_CHASE_VIEWPOINT);

    mSavedState = mState;
}

/*****************************************************************************
 * Set the target Object that the chase-plane looks at.
 *****************************************************************************/
void
ChaseViewpoint::SetTarget( shptr<Object> target )
{
CHECK_TYPESIG(this,TYPESIG_CHASE_VIEWPOINT);
CHECK_TYPESIG(target,TYPESIG_OBJECT);

    mState.mTarget = target;
}

shptr<Object>
ChaseViewpoint::GetTarget( void )
{
CHECK_TYPESIG(this,TYPESIG_CHASE_VIEWPOINT);
CHECK_TYPESIG(mState.mTarget,TYPESIG_OBJECT);

    return mState.mTarget;
}

/*****************************************************************************
 * Set the distance between the chase-plane and what it looks at.
 * @param   distance_
 *          Distance in world space.
 *****************************************************************************/
void
ChaseViewpoint::SetDistance( const Vector3& distance_ )
{
CHECK_TYPESIG(this,TYPESIG_CHASE_VIEWPOINT);

    // These rotations are for initially orienting
    // the chase-plane behind its target.
    // The 90' pitch is because the up-vector of the ellipsoid
    // would make Craft point upwards.
    Matrix m;
    MatrixRotateLocal( m, XX, Deg2Rad(90.f) );    // pitch
    MatrixRotateLocal( m, YY, Deg2Rad(-180.f) );  // from forward to behind
    Vector3 distance = distance_ * m;

    // KLUDGE: Scale distance by WORLD_CONF.mObjectScale.
    // That is, if ObjectScale is 4x, then chase-plane distance should be 4x.
    mState.mDistance          = \
    mState.mDistanceUnrotated = distance * WORLD_CONF.mObjectScale;
}

Vector3
ChaseViewpoint::GetDistance( void )
{
CHECK_TYPESIG(this,TYPESIG_CHASE_VIEWPOINT);

    return mState.mDistance;
}

/*****************************************************************************
 * Move the chase-plane nearer/further to target.
 * @param   inc
 *          Increment in world space.
 *****************************************************************************/
void
ChaseViewpoint::Move( const fp inc )
{
CHECK_TYPESIG(this,TYPESIG_CHASE_VIEWPOINT);

    // Prevent vectors becoming zero to avoid NaN or divide-by-zero errors.
    if ( (inc > 0.0f) or (Distance(mState.mDistanceUnrotated) > ABS(inc*2)) )
    {
        // Recompute rotated/unrotated distances.
        mState.mDistance          = math::AddDistance( mState.mDistance,          inc );
        mState.mDistanceUnrotated = math::AddDistance( mState.mDistanceUnrotated, inc );
    }
}

/*****************************************************************************
 * Rotate chase-plane.
 *****************************************************************************/
void
ChaseViewpoint::Rotate( const uint axis, const Radian rad )
{
CHECK_TYPESIG(this,TYPESIG_CHASE_VIEWPOINT);

    // Rotating the chase-plane is done by transforming its 3D vector
    // (distance between it and target) thru a matrix that is rotated.
    if ( axis == AXIS_CHASE_PLANE_HORZ )
        mState.mHorzAngle = mState.mHorzAngle + rad;
    else if ( axis == AXIS_CHASE_PLANE_VERT )
        mState.mVertAngle = mState.mVertAngle + rad;
    else
        ASSERT(false);

    // Compute chase-plane's matrix.
    Matrix m;
    MatrixRotateLocal<>( m, AXIS_CHASE_PLANE_HORZ, mState.mHorzAngle );
    MatrixRotateLocal<>( m, AXIS_CHASE_PLANE_VERT, mState.mVertAngle );

    // Transform the unrotated vector thru the rotated matrix.
    // (Passing the rotated vector as input isn't right)
    mState.mDistance = mState.mDistanceUnrotated * m;

    // Then Viewpoint::ComputeMatrix() will use mDistance with makeLookAt().
}

////////////////////////////////////////////////////////////////////////////////
/////////////////////////  ChaseViewpoint::State  //////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor.
 *****************************************************************************/
ChaseViewpoint::State::State( void )
:   mTarget(Object::MakeDummyObject()),
    mDistance(0,0,0),
    mDistanceUnrotated(0,0,0),
    mHorzAngle(0.0f),
    mVertAngle(0.0f)
{
    // NOP
}

} // namespace view
