/*****************************************************************************
 * $Id: eng_view.cc,v 1.96 2006/08/10 03:18:58 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	View classes.
 * @remarks	For 2D drawing, the geometry passed to GFX_BEGIN_2D(Geom*) is relative
 *          (contained in) the viewport geometry passed to View::SetViewportGeom().
 *//*
 * LEGAL:	COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#define ENG_VIEW_CC 1
#include "eng.hh"
#include "eng_view.hh"

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

uint View::msAllFrameCnt;
uint View::msViewCnt;		// for external code

/*****************************************************************************
 * View constructor.
 *****************************************************************************/
View::View( ViewMaster& viewMaster )
:	mViewMaster(viewMaster)
{
	mViewNum		= msViewCnt++; ASSERT( msViewCnt < VIEWS_MAX );
	mRenderPass		= 0;
	// N/A rectTestViewport
	mVisible		= false;
	mGeomViewport.Clear();
	mLocusX			= 0;
	mLocusZ			= 0;
	ResetFrameCnt(); // initializes mViewFrameCnt, mTrueFrameCnt
}

/*****************************************************************************
 * Reset per-view frame counts.
 * Every view should have different frame counts.
 * The reason is that outside code that examines frame counts
 * and bypasses rendering or calculating when the frame count hasn't changed,
 * which is wrong is multiple views are to be rendered in the same actual frame.
 *****************************************************************************/
void
View::ResetFrameCnt( void )
{
	mViewFrameCnt = FRAME_CNT_INIT + (mViewNum + 1) * 100*1000;
	mTrueFrameCnt = FRAME_CNT_INIT;
}

/*****************************************************************************
 * Draw methods.
 *****************************************************************************/
void
View::PreDraw( void )
{
	if ( mVisible )
	{
		gEng.mEye.mChanged = false;
	}
}
void
View::Draw( void )
{
	if ( mVisible )
	{
		GrabFocus();			// sets ViewMaster::mCur
		gEng.DrawObjects();
	}
}
void
View::PostDraw( void )
{
	IncFrameCnt();		// per view
	++msAllFrameCnt;	// all views
}

/*****************************************************************************
 * Grab focus.
 *****************************************************************************/
void
View::GrabFocus( void )
{
	mViewMaster.SwitchFocus( this );
}

/*****************************************************************************
 * Set viewport geometry of a View object.  All args (x,y,w,h) should be positive.
 * This doesn't affect the actual gfxsys viewport (this just changes View members)
 * which is useful for reshaping inactive View objects.
 * The gfxsys viewport can be changed by GfxDriverSetViewportGeom().
 * @verbatim
 *  +Y
 *  +------+
 *  |      |
 *  |      |
 *  +------+ +X
 *  ^origin
 * @endverbatim
 *****************************************************************************/
void
View::SetViewportGeom( int x, int y, int w, int h )
{
	// This method only affect View members, not the actual gfxsys viewport.
	mGeomViewport.Set( x, y, w, h );

	// Precompute viewport rectangle as an optimization for TestViewport().
	const int margin = 100;
	mRectTestViewport.x1 = x - margin;
	mRectTestViewport.y1 = y - margin;
	mRectTestViewport.x2 = w + margin;
	mRectTestViewport.y2 = h + margin;
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////  ViewMaster  //////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ViewMaster constructor.
 *****************************************************************************/
ViewMaster::ViewMaster( void )
{
	mReshaped		= false;
	mGeom.Set( 0, 0, MAIN_WINDOW_WIDTH, MAIN_WINDOW_HEIGHT );
	mCur			= NULL;
	mRedisplayInput	= 0;
	mBusyRendering	= 0;
	mFreeze			= false;
	ResetFPS();
}

/*****************************************************************************
 * Add a view.
 *****************************************************************************/
void
ViewMaster::AddView( ViewPtrc view )
{
	mViews.push_back( view );
}
void
ViewMaster::AddViewPrimary( ViewPtrc view )
{
	mViews.push_front( view );
	mViewPrimary = mCur = view;
}

/*****************************************************************************
 * Reset all views.
 *****************************************************************************/
void
ViewMaster::Reset( void )
{
	// reset members:
	mCur			= mViewPrimary;
	mRedisplayInput	= 0;
	mBusyRendering	= 0;
	mFreeze			= false;
	ResetFPS();

	// Reset Views.
	ViewItr itr;
	for ( itr = mViews.begin(); itr != mViews.end(); ++itr )
		(*itr)->Reset();

	ResetFrames();
}

/*****************************************************************************
 * Reset frame count in all views.
 *****************************************************************************/
void
ViewMaster::ResetFrames( void )
{
	View::msAllFrameCnt = FRAME_CNT_INIT;
	ViewItr itr;
	for ( itr = mViews.begin(); itr != mViews.end(); ++itr )
		(*itr)->ResetFrameCnt();
	ResetFPS();
}

/*****************************************************************************
 * Draw all views.  Called by the main rendering routine.
 * TODO: Some future views such as chase-plane will need to change the Eye matrix
 * (at PreDraw()) but should restore (at PostDraw()) so that other views tha
 * don't have their own Eye matrix will access gEng.mEye as it is for the primary view.
 *****************************************************************************/
void
ViewMaster::DrawViews( void )
{
	// Skip drawing until geometry is set.
	if ( ! mReshaped ) return;

GfxDriverDisplayPreFrame();
++mBusyRendering;

	gEngineEvent.mPreDrawViews.Call();
	ENGINE_STATS_CODE( gEng.mStats.PreDrawViews(); )

	// Draw each view.
	ASSERT( ! mViews.empty() );
	ViewItr itr;
	for ( itr = mViews.begin(); itr != mViews.end(); ++itr )
	{
		(*itr)->PreDraw();
		(*itr)->Draw();
		(*itr)->PostDraw();
	}

	// Switch back to primary view so that subsequent rendering
	// will affect Eye object associated with the primary view.
	SwitchFocus( mViewPrimary );

	gEngineEvent.mPostDrawViews.Call();

--mBusyRendering;
GfxDriverDisplayPostFrame();
}

/*****************************************************************************
 * Called by the gfxsys when it reshapes the window.
 * @note Don't confuse viewport/window geometry.
 *****************************************************************************/
void
ViewMaster::Reshape( int w, int h )
{
	// Change geometry.
	//  +Y
	//  +------+
	//  |      |
	//  |      |
	//  +------+ +X
    //  ^origin

	mReshaped = true;
	mGeom.Set( 0, 0, w, h );
	GfxDriverSetViewportGeom( mGeom );

	// Change viewport geometry of other views relative to window geometry.
	ViewItr itr;
	for ( itr = mViews.begin(); itr != mViews.end(); ++itr )
		(*itr)->Reshape( w, h );

	gEngineEvent.mReshape.Call( mGeom );
}

/*****************************************************************************
 * Call this in order to render into a View object.
 * Check visibility of incoming object first.
 *****************************************************************************/
void
ViewMaster::SwitchFocus( ViewPtrc view )
{
	if ( mCur != view )
	{
		mCur = view;

		// If this is the primary view, use the ViewMaster geometry.
		// Why?  The View::mGeom might be stale.
		GfxDriverSetViewportGeom( view == mViewPrimary ? mGeom : view->mGeomViewport );
	}
}

/*****************************************************************************
 * Update FPS.
 *****************************************************************************/
void
ViewMaster::UpdateFPS( void )
{
	const int milsec = gEng.GetMilsecFrame(); // time between current/previous timer-ticks

	mFPSMilsecCountdown -= milsec;
	if ( mFPSMilsecCountdown <= 0 )
	{
		// Use frame count of primary view only (don't inflate FPS).
		int frameCnt = mViewPrimary->GetTrueFrameCnt();
		mFPS = frameCnt - mFPSPrevFrameCnt;
		mFPSPrevFrameCnt = frameCnt;
		RESET_COUNTDOWN( mFPSMilsecCountdown, 1000 );
	}
}
void
ViewMaster::ResetFPS( void )
{
	mFPS				= 0;
	mFPSMilsecCountdown = 1000;
	mFPSPrevFrameCnt	= 0;
}
