/*****************************************************************************
 * $LastChangedDate: 2010-06-26 21:01:07 -0400 (Sat, 26 Jun 2010) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   View class.
 *
 * Matrix optimization requires syncing matrixs:
 * For speed, the modelview and projection matrixs are stored/cached in View members
 * This allows GetViewMatrix() to return a const Matrix& instead of a copy of Matrix.
 * The disadvantage/risk is having to ensure View and OSG's matrixs stay synchronized.
 * 
 *//*
 * LEGAL:   COPYRIGHT (C) 2007 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#define VIEW_VIEW_CC 1
#include "base/module.hh"
using namespace base;
#include "math/module.hh"
#include "math/graphics.hh"
#include "math/funcs_transform.hh"
using namespace math;
#include "view/module.hh"
#include "view/view.hh"
#include "view/node_masks.hh"
#include "view/conf.hh"
#include <osg/Camera>

namespace view {

const RGBA VIEW_OUTLINE_COLOR( 0.8f, 0.8f, 0.2f, 1.0f );

// Sync View's matrix with a change in osg::Camera's matrix.
#define SYNC_MATRIX_FROM_OSG_CAMERA() mViewMatrix = mCamera->getViewMatrix()

////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////  View  ///////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor.
 * See View::SetRect() for definition of rectangle.
 *****************************************************************************/
View::View( const bool enable,
            const Rect<int>& rect,
            shptr<Viewpoint> viewpoint,
            RefPtr<osg::Camera> camera )
:   mEnabled(enable),
    mRect(rect),
    mViewpoint(viewpoint),
    mCamera(camera),
    mViewMatrix(camera->getViewMatrix()),
    mProjectionMatrix(camera->getProjectionMatrix()),
    mComputedViewMatrix(false),
    mRollDegree(0.0f),
    mOutlineColor(VIEW_OUTLINE_COLOR)
{
CHECK_TYPESIG(viewpoint,TYPESIG_VIEWPOINT);

    SET_TYPESIG(this,TYPESIG_VIEW);

    // Apply rectangle to gfxsys.
    SetRect( rect );

    // Prevent frustum clipping at near plane.
    mCamera->setNearFarRatio( VIEW_CONF.mViewNearFarRatio );
}

View::~View()
{
    INVALIDATE_TYPESIG(this,TYPESIG_VIEW);
}

////////////////////////////////////////////////////////////////////////////////
/////////////////////////////  View : enablement  //////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Enable/disable View.
 *****************************************************************************/
void
View::Enable( const bool enable )
{
CHECK_TYPESIG(this,TYPESIG_VIEW);

    // Update var.
    mEnabled = enable;

    // Enable/disable Camera in scene graph.
    // Note that setNodeMask() differs from setCullMask().
    mCamera->setNodeMask( enable ? ~0 : 0 );
}

/*****************************************************************************
 * Enable/disable rendering a 3D model in the main view.
 * This is used to disable the player's aircraft model in the main view,
 * while rendering it in the other views (which show chase view).
 *
 * Implementation details:
 * Although Object::GetRootNode() returns Graph::GetRootNode(),
 * this has a per-Object effect as every Graph has its own root node.
 * ModelCache returns a new Graph every time.
 *****************************************************************************/
void // CLASS_METHOD
View::EnableObjectInMainView( Object& object, const bool enableInMainView )
{
CHECK_TYPESIG(&object,TYPESIG_OBJECT);

    osg::Node::NodeMask nodeMask = object.GetRootNode()->getNodeMask();
    if ( enableInMainView )
    {
        // Restore main-view bits from aux-view bits.
        // Copy bit-field into another.
        osg::Node::NodeMask nodeMask2 = nodeMask & defs::NODE_MASK_AUX_VIEW_ALL;
        nodeMask2 >>= defs::NODE_MASK_AUX_VIEW_SHIFT;
        nodeMask2 <<= defs::NODE_MASK_MAIN_VIEW_SHIFT;
        nodeMask &= ~defs::NODE_MASK_MAIN_VIEW_ALL;
        nodeMask |= nodeMask2;
    }
    else
    {
        // Clear all bits of main-view.
        nodeMask &= ~defs::NODE_MASK_MAIN_VIEW_ALL;
    }
    object.GetRootNode()->setNodeMask( nodeMask );
}

////////////////////////////////////////////////////////////////////////////////
//////////////////////  View : view matrix, viewpoint  /////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Set/get the modelview matrix.
 *****************************************************************************/
void
View::SetViewMatrix( const Matrix& m )
{
CHECK_TYPESIG(this,TYPESIG_VIEW);

    // Set matrix of this View object.
    mViewMatrix = m;

    // And set matrix in OSG Camera.
    mCamera->setViewMatrix( m );  // Matrixf[loat]
}

/*****************************************************************************
 * Recompute view matrix.
 * This is called every frame by the graphics main loop.
 * Defers to the Viewpoint (a functor).
 *****************************************************************************/
void
View::ComputeViewMatrix( void )
{
CHECK_TYPESIG(this,TYPESIG_VIEW);

    // First save the view matrix.
    const Matrix prevMatrix = GetViewMatrix();

    // Change the view matrix.
    // Computing the view matrix is the purpose of the Viewpoint object.
    const Matrix nextMatrix = mViewpoint->ComputeViewMatrix( this ); 
    SetViewMatrix( nextMatrix );

    // To support cloud sprites, compute the "roll degree" between
    // the previous/next view matrixs.
    if ( mComputedViewMatrix )
    {
        const Degree rollDegreeDelta = math::ComputeMatrixRollDegree( prevMatrix, nextMatrix );
        mRollDegree = mRollDegree + rollDegreeDelta;
    }
    mComputedViewMatrix = true;
}

/*****************************************************************************
 * @return Position of view matrix in world space.
 *****************************************************************************/
WorldVertex
View::GetPosition( void )
{
CHECK_TYPESIG(this,TYPESIG_VIEW);

    // View matrix has negative origin.
    return WorldVertex( ABS(mViewMatrix[Ox]),
                        ABS(mViewMatrix[Oy]),
                        ABS(mViewMatrix[Oz]) );
}

/*****************************************************************************
 * Switch the Viewpoint object.
 *****************************************************************************/
void
View::SetViewpoint( shptr<Viewpoint> viewpoint )
{
CHECK_TYPESIG(this,TYPESIG_VIEW);
CHECK_TYPESIG(viewpoint,TYPESIG_VIEWPOINT);

    mViewpoint = viewpoint;
}

/*****************************************************************************
 * Look at a point.
 *****************************************************************************/
void
View::LookAt( const Vector3& target, const Vector3& viewpoint )
{
CHECK_TYPESIG(this,TYPESIG_VIEW);

    // man gluLookat
    mCamera->setViewMatrixAsLookAt( viewpoint,                 // viewpoint
                                    target,                    // center
                                    Vector3(0.0f,0.0f,1.0f));  // up (Z)
    SYNC_MATRIX_FROM_OSG_CAMERA();
}

////////////////////////////////////////////////////////////////////////////////
////////////////////  View : transformation, projection  ///////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Project a 3D vertex to a 2D point.
 *
 * Gollum originally wrote this function.
 *
 * This is a highly-optimized version of Mesa 6.3.2 gluProject().
 * The major improvement is bypassing multiplying the modelview matrix
 * since Palomino identity-maps it.
 * Minor improvements are streamling the function args, bypassing unneeded
 * calculations, and using single-precision.
 * 
 * GLint GLAPIENTRY
 * gluProject(GLdouble objx, GLdouble objy, GLdouble objz, 
 *           const GLdouble modelMatrix[OPENGL_MATRIX_ELEMS], 
 *           const GLdouble projMatrix[OPENGL_MATRIX_ELEMS],
 *           const GLint viewport[4],
 *           GLdouble *winx, GLdouble *winy, GLdouble *winz)
 * {
 *     double in[4];
 *     double out[4];
 * 
 *     in[0]=objx;
 *     in[1]=objy;
 *     in[2]=objz;
 *     in[3]=1.0;
 *     __gluMultMatrixVecd(modelMatrix, in, out);
 *     __gluMultMatrixVecd(projMatrix, out, in);
 *     if (in[3] == 0.0) return(GL_FALSE);
 *     in[0] /= in[3];
 *     in[1] /= in[3];
 *     in[2] /= in[3];
 *     // Map x, y and z to range 0-1
 *     in[0] = in[0] * 0.5 + 0.5;
 *     in[1] = in[1] * 0.5 + 0.5;
 *     in[2] = in[2] * 0.5 + 0.5;
 * 
 *     // Map x,y to viewport
 *     in[0] = in[0] * viewport[2] + viewport[0];
 *     in[1] = in[1] * viewport[3] + viewport[1];
 * 
 *     *winx=in[0];
 *     *winy=in[1];
 *     *winz=in[2];
 *     return(GL_TRUE);
 * }
 * 
 * INTERN void __gluMultMatrixVecd(const GLdouble matrix[OPENGL_MATRIX_ELEMS], const GLdouble in[4],
 *               GLdouble out[4])
 * {
 *     int i;
 * 
 *     for (i=0; i<4; i++) {
 *     out[i] = 
 *         in[0] * matrix[0*4+i] +
 *         in[1] * matrix[1*4+i] +
 *         in[2] * matrix[2*4+i] +
 *         in[3] * matrix[3*4+i];
 *     }
 * }
 *****************************************************************************/
Vector2
View::Project( const WorldVertex& wv )
{
CHECK_TYPESIG(this,TYPESIG_VIEW);
ASSERT( not mRect.IfZero() );

    const EyeVertex ev = math::TransformWorld2Eye( wv, mViewMatrix );  // transform world --> eye
    const Matrix&   pm = mProjectionMatrix;                            // transform eye --> 2D

    // pr[2] doesn't contribute to the resulting 2D point.
    fp pr[4];

    pr[0] = ev[XX]*pm[ 0] + ev[YY]*pm[ 4] + ev[ZZ]*pm[ 8] + pm[12];
    pr[1] = ev[XX]*pm[ 1] + ev[YY]*pm[ 5] + ev[ZZ]*pm[ 9] + pm[13];
//  pr[2] = ev[XX]*pm[ 2] + ev[YY]*pm[ 6] + ev[ZZ]*pm[10] + pm[14];
    pr[3] = ev[XX]*pm[ 3] + ev[YY]*pm[ 7] + ev[ZZ]*pm[11] + pm[15];

    pr[0] /= pr[3];
    pr[1] /= pr[3];
//  pr[2] /= pr[3];

    // Map x,y,z to range {0,..,1}.
    pr[0] = pr[0] * 0.5 + 0.5;
    pr[1] = pr[1] * 0.5 + 0.5;
//  pr[2] = pr[2] * 0.5 + 0.5;

    // Map x,y to viewport.
//  pt[XX] = (int) (pr[0] * mRect[2] + mRect[0]);
//  pt[YY] = (int) (pr[1] * mRect[3] + mRect[1]);
    return Vector2( pr[0] * mRect.x2,
                    pr[1] * mRect.y2 );
}

/*****************************************************************************
 * @return True if 3D position is in front of viewpoint.
 *****************************************************************************/
bool
View::IF_Z_FACING( const WorldVertex& wv )
{
CHECK_TYPESIG(this,TYPESIG_VIEW);

    const EyeVertex ev = TransformWorld2Eye( wv );
    return math::IF_Z_FACING( ev[ZZ] );
}

////////////////////////////////////////////////////////////////////////////////
/////////////////////////////  View : dimensions  //////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Set dimensions of viewport.
 * (x1,y1) = bottom-left
 * (x2,y2) = top-right
 * Relative to BOTTOM-LEFT of window.
 * Greater coordinates go toward top-right as in math/OpenGL.
 * Viewport is allowed to be outside window.
 *****************************************************************************/
void
View::SetRect( const Rect<int>& rect )
{
CHECK_TYPESIG(this,TYPESIG_VIEW);
ASSERT( rect.x2 > rect.x1 );
ASSERT( rect.y2 > rect.y1 );

    // Update var.
    mRect = rect;

    // Apply change to gfxsys.
    // Set viewport where camera renders into.
    const int w = rect.x2 - rect.x1;  // convert to width
    const int h = rect.y2 - rect.y1;  // convert to height
    ASSERT( (w > 0) and (h > 0) );
    OsgGetCamera()->setViewport( rect.x1, rect.y1, w, h );
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////  View : sprite support  ///////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Support for cloud sprites.
 *****************************************************************************/
Degree
View::GetRollDegree( void )
{
CHECK_TYPESIG(this,TYPESIG_VIEW);

    return mRollDegree;
}

} // namespace view
