/*****************************************************************************
 * $Id: eng.cc,v 1.388 2006/08/17 00:07:03 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Palomino 3D engine.
 * @verbatim
 * ------------------------------------------------------------------------------
 * Render loop:
 *
 * The render loop is driven by a timer or a redisplay event.
 * Animation is driven by the same timer, and animation drives rendering.
 * Functions called after timer tick:
 * - OpenGL timer event
 * - Engine::Tick()
 * - Engine::Animate()       // usually redisplay=true, if false, rendering is skipped
 * - GFX_POST_REDISPLAY()    // force redisplay
 * - OpenGL redisplay event  // also is starting point if window needs repaint
 * - Engine::Render()
 *
 * The render loop sorts only visible objects and excludes invisible objects.
 * Therefore, usually, Draw() methods no longer should call Visible().
 * An exception is Objects that are part of an ObjectGroup as the render loop
 * only tests the visibility of ObjectGroup, not individual Objects.
 *
 * The render loop guarantees:
 * - Object::Draw() is only called if Object is visible.
 * - Omni::Draw() is called if Omni is visible and only once per frame.
 *   Omni objects (eg Sky) exist in every quadrant, but are to be rendered once per frame.
 * - Does NOT guarantee the visibility of individual Objects contained by an ObjectGroup.
 *   Grouped Objects are excluded by (hidden from) the render loop, so their Draw()
 *   methods still must call Visible() themselves.
 * ------------------------------------------------------------------------------
 *//*
 * LEGAL:   COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#define ENG_CC 1
#include "eng.hh"
#include "eng_debug.hh"

////////////////////////////////////////////////////////////////////////////////
// Wrapper class used for sorting objects.
// The distance^2 is stored to avoid recalculating the distance
// when the STL compares the objects while sorting.
struct SortedObject
{
	SortedObject( Object* obj ) : mObj(obj), mDistance2(obj->Distance2()) { }
	Object* mObj;
	fp		mDistance2;
};
static inline bool operator<( const SortedObject& o1, const SortedObject& o2 )
{
	// Yes, greater than, so that the most distant object is first in sorted container.
	return o1.mDistance2 > o2.mDistance2;
}

// Holds objects sorted by distance from eye.
// Sorting is necessary for proper alpha-blending.
static std::vector<SortedObject> sSortedObjects;

/*****************************************************************************
 * Main rendering routine.
 * Called by graphics system to render a frame.
 *****************************************************************************/
void
Engine::Render( void )
{
	if ( ! mBusy )
	{
		++mBusy;
		Object::StartFrame();
		Dyna::StartFrame();
		mView.DrawViews();
		--mBusy;
	}
}

/*****************************************************************************
 * Draw objects in locus of quadrants.
 *
 * In order to properly draw objects with translucent alpha-blended polygons
 * (eg cockpit glass), all other opaque objects must be drawn first.
 * Rather than having two passes, for speed, the Craft class is specially
 * iterated thru after the first pass as currently only Craft objects
 * use alpha-blending for translucency.  However, the variable View::renderPass
 * does have two values as if two passes were done in case later two passes
 * becomes really necessary.
 *****************************************************************************/
void
Engine::DrawObjects( void )
{
	mView.mCur->mRenderPass = 1;
	gWorld.mLocus->Update();

	SortObjects();			// sSortedObjects
	DrawSortedObjects();	// sSortedObjects

	mView.mCur->mRenderPass = 2;
	gEngineEvent.mDrawObjects.Call();
	mView.mCur->mRenderPass = 0;
	KillZombies();
}

/*****************************************************************************
 * Sort objects in locus.
 * @param	[sSortedObjects]
 *****************************************************************************/
void
Engine::SortObjects( void )
{
	// Subtle: Objects contained by ObjectGroups (and associated shadow Quadrants)
	// are directly excluded by (hidden from) the render loop.  Since only
	// the visibility of the ObjectGroup was tested here, this is why
	// grouped Objects still need to call Visible() in their Draw() method.

	// Clear and pre-allocate STL vector.
	sSortedObjects.clear();
	sSortedObjects.reserve( gWorld.mLocus->GetObjectCnt() << 1 );

	// Fill a STL vector of Objects in the locus that are visible.
	Quadrant* firstVisibleQuadrant = NULL;
	for ( QuadrantItr itrQuadrant = gWorld.mLocus->mQuadrants.begin();
          itrQuadrant != gWorld.mLocus->mQuadrants.end();
          ++itrQuadrant )
	{
		Quadrant* quadrant = *itrQuadrant;

		// Skip objects in non-visible quadrants.
		if ( quadrant->IfVisible() )
		{
			// Remember the first quadrant encountered that's visible.
			if ( firstVisibleQuadrant == NULL )
				firstVisibleQuadrant = quadrant;

			const Dlist<Object>& objDlist = quadrant->GetObjectDlist();
			for ( const Dlink<Object>* dlink = objDlist.ForStart();
			      objDlist.ForTest(dlink);
			      objDlist.ForNext(dlink) )
			{
				// Append Object to STL vector.
				// Only include Omni objects for the first visible quadrant
				// (Omnis shouldn't be drawn for every quadrant).
				Object* obj = dlink->mObj;
				if (    obj->Visible()
                     && ! (obj->IfOmni() && quadrant != firstVisibleQuadrant) )
				{
					sSortedObjects.push_back( SortedObject(obj) );
				}
			}
		}
	}

	// Sort objects by distance.
	// STL sort() is typically implemented as quicksort.
	std::sort( sSortedObjects.begin(), sSortedObjects.end() );
}

/*****************************************************************************
 * Render sorted objects in locus.
 * @param	[sSortedObjects]
 *****************************************************************************/
void
Engine::DrawSortedObjects( void )
{
	// Render sorted objects.
	std::vector<SortedObject>::iterator itrObj;
	for ( itrObj = sSortedObjects.begin(); itrObj != sSortedObjects.end(); ++itrObj )
	{
		// Render this object.
		Object* obj = (*itrObj).mObj; CHECK_TYPESIG( obj, TYPESIG_OBJECT );
		DEBUG_CODE( SetObjectNameRendering( obj ); )
		obj->Draw();
	}
}

/*****************************************************************************
 * Main animation routine.
 * Called periodically by a timer.
 *****************************************************************************/
void
Engine::Animate( void )
{
	if ( ! GFX_IF_READY() || mView.IfFreeze() ) return;

	mView.UpdateFPS();

	// Skip rendering if the scene hasn't changed.
	mRedisplay = 0;
	gEngineEvent.mAnimate.Call(); // may affect Engine::mRedisplay
	if ( mRedisplay ) GFX_POST_REDISPLAY();
}

/*****************************************************************************
 * Every timer tick.
 * @param	milsecFrame
 *			Elapsed time relative to the previous timer-tick.
 * @param	milsecTotal
 *          Elapsed time relative to the start of the program.
 *****************************************************************************/
void
Engine::Tick( int milsecFrame, int milsecTotal )
{
	mMilsecFrame = milsecFrame;
	mMilsecTotal = milsecTotal;
	DEBUG_CODE( if ( mTestFrameRate ) { TickTestFrameRate(); return; } )
	gEngineEvent.mTick.Call();
	Animate();
}

/*****************************************************************************
 * Reset scene.
 *****************************************************************************/
static void
ResetSceneObject( Object* obj )
{
	obj->Reset();
}
void
Engine::ResetScene( void )
{
	mEye.Reset();
	mView.Reset();
	SetCurrent( &mEye );
	gWorld.ForEachObject( ResetSceneObject );
	gEngineEvent.mResetScene.Call();
	GFX_POST_REDISPLAY();
}
