/*****************************************************************************
 * $Id: eng_object.cc,v 1.186 2006/11/25 23:17:57 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Object base class methods and top-level Object code.
 *//*
 * LEGAL:	COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#define ENG_OBJECT_CC 1
#include "eng.hh"
#include "eng_object.hh"
#include "gfx_stream.hh"

////////////////////////////////////////////////////////////////////////////////
////////////////////////  Object, ObjectData classes  //////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ObjectData ctor/dtor.
 *****************************************************************************/
ObjectData::ObjectData( QuadrantPtrConst q ) 
{
	mLinkObject	= q->AddObject( this );	// Quadrant maintains its object count
	mQuadrant	= q;
	mRadius		= 0;

	gWorld.IncObjectCnt();
	DEBUG_CODE( CheckObjectCtor(); )
}
// This destructor should be optimal as Objects are constructed/destroyed often (hi-freq).
ObjectData::~ObjectData()
{
	// Eye is a static object.  When ~Eye() is called, everything's gone.
	if ( UX(this == &gEng.mEye) )
		return;

	// Remove from object list.
	CHECK_TYPESIG(mQuadrant,TYPESIG_QUADRANT);
	mQuadrant->RemoveObject( mLinkObject );  // Quadrant maintains its object count

	gWorld.DecObjectCnt(); ASSERT( gWorld.GetObjectCnt() >= 0 );

	DEBUG_CODE( CheckObjectDtor(); )
}

/*****************************************************************************
 * Debug.
 *****************************************************************************/
DEBUG_CODE( int ObjectData::mInstanceCnt; )
#if DEBUG
void
ObjectData::CheckObjectCtor( void )
{
	// Exclude static Eye as its dtor is called when everything's gone.
	if ( UX(this != &gEng.mEye) )
	{
		CHECK_TYPESIG( GetQuadrant(), TYPESIG_QUADRANT );
		++mInstanceCnt; ASSERT(mInstanceCnt < MAX_OBJECTS );
	}
}
void
ObjectData::CheckObjectDtor( void )
{
	if ( UX(this != &gEng.mEye) )
	{
		CHECK_TYPESIG( GetQuadrant(),TYPESIG_QUADRANT );
		--mInstanceCnt; ASSERT(mInstanceCnt >= 0 );
	}
}
#endif // DEBUG

/*****************************************************************************
 * An object destroys itself.
 *
 * To avoid the bug of altering a list while iterating thru it:
 * This used to unlink an object out of the Quadrant's list of objects
 * by calling Quadrant::RemoveObject().  To avoid disrupting iterating
 * thru that list, unlinking is deferred to KillZombies() (~Object() actually).
 * KillZombies() is run at a safe-time outside of the render loop.
 *****************************************************************************/
void
Object::SelfDestruct( void ) 
{
	// Append to zombie list.
	// The reason for the zombie list is that C++ objects can't delete themselves.
	if ( ! gEng.mZombieDlist.IfLinked( this ) )  // DO NOT RELINK THE SAME OBJECT!
		gEng.mZombieDlist.Append( this );
}

/*****************************************************************************
 * Delete zombie objects.  Zombie objects are objects that were unlinked
 * from the object list (no longer drawn/animated).  A list of zombies
 * is used because a C++ object can't delete itself.
 *****************************************************************************/
// This Engine method relates to Object destruction.
void
Engine::KillZombies( void )
{
	// Clearing the zombie list will delete its linked Objects.
	mZombieDlist.Clear();
}

/*****************************************************************************
 * @return Distance from object's furthest extent to viewplane.
 * GetDistance() is intended for use in sorting objects.
 * Subtlety: Distance from viewplane and viewpoint aren't exactly the same.
 * For sorting objects, distance from viewplane is preferred.
 *****************************************************************************/
#define DISTANCE_FROM_POSITION()  (Z_TO_DISTANCE(gEng.TransformZ(GetPosition())))
fp
Object::GetDistance( void )
{
	// The negated transformed Z coordinate is more accurate
	// than the Pythagoran geometry distance for sorting objects.
	// Z_TO_DISTANCE() expands a minus sign if -Z is line-of-sight.
	//
	// Add radius to cover furthest extent of Object.
	return DISTANCE_FROM_POSITION() + GetRadius();
}
// Ignores radius.  Appropriate for billboards (have no radius in context of sorting).
fp
Object::GetDistanceFromPosition( void )
{
	return DISTANCE_FROM_POSITION();
}
#undef DISTANCE_FROM_POSITION

////////////////////////////////////////////////////////////////////////////////
////////////////////////////  ObjectMesh class  ////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Object constructor.
 *****************************************************************************/
ObjectMesh::ObjectMesh( QuadrantPtrConst q )
	: Parent(q)
{
	mLinkDetector = NULL;
	DEBUG_CODE( CheckObjectCtor(); )
}
ObjectMesh::~ObjectMesh()
{
	// Delete Detector object (unless it wasn't constructed).
	if ( UX(mLinkDetector != NULL) )
	{
		// Order-dependent.
		Detector* detector = mLinkDetector->mObj;
		CHECK_TYPESIG( GetQuadrant(), TYPESIG_QUADRANT );
		GetQuadrant()->RemoveDetector( mLinkDetector );  // unlink from list
		delete detector;
		mLinkDetector = NULL;
	}
	DEBUG_CODE( CheckObjectDtor(); )
}

/*****************************************************************************
 * Calculate the radius of an object, and adjust its position to its center.
 * Required by Object::Visible().
 * @param	vcnt
 * @param	wv
 * @param	cube
 *			This function grows a cube that encloses the Object.
 * @param	adjustPosition
 *			False if bypass adjusting position (just calculate radius).
 *****************************************************************************/
// Object::CalcRadius*() don't depend on object having a mesh but ObjectMesh::CalcRadius*() do.
void
Object::CalcRadiusAdjustPosition( int vcnt, const WorldVertex* wv, Cube<fp>& cube/*OUT*/, bool adjustPosition )
{
	ASSERT( vcnt > 0 );
	ASSERT( wv != NULL );

	// The radius/center of an object can be deduced from its world vertexs.
	cube.Set( wv[0] );
	for ( int i = 1; i < vcnt; ++i )
		cube.Grow( wv[i] );

	// Set radius.
	SetRadius( cube.GetRadius() * 1.1 );

	// Adjust position to center of cube.
	if ( adjustPosition )
	{
		Position pos = cube.GetCenter();
		SetPosition( pos );
	}
}
void
Object::CalcRadius( int vcnt, const WorldVertex* wv, Cube<fp>& cube )
{
	CalcRadiusAdjustPosition( vcnt, wv, cube, false );
}
// ObjectMesh::CalcRadius*() depend on object having a mesh.
void
ObjectMesh::CalcRadiusAdjustPosition( Cube<fp>& cube )
{
	CalcRadiusAdjustPosition( GetVertexCnt(), GetWorldVertexs(), cube, true );
}
void
ObjectMesh::CalcRadiusAdjustPosition( void )
{
    static Cube<fp> cube;
	CalcRadiusAdjustPosition( GetVertexCnt(), GetWorldVertexs(), cube, true );
}
void
ObjectMesh::CalcRadius( void )
{
    static Cube<fp> cube;
	CalcRadiusAdjustPosition( GetVertexCnt(), GetWorldVertexs(), cube, false );
}

/*****************************************************************************
 * Object cache methods.
 *****************************************************************************/
WorldVertex*
ObjectMesh::GetCacheWorldVertex( void )
{
	return gEng.GetCacheWorldVertex( &mCacheOpqWorldVertex );
}
void
ObjectMesh::PutCacheWorldVertex( WorldVertex* wv )
{
	gEng.PutCacheWorldVertex( &mCacheOpqWorldVertex, wv );
}
Normal*
ObjectMesh::GetCacheNormals( void )
{
	return gEng.GetCacheNormals( &mCacheOpqNormals );
}
void
ObjectMesh::PutCacheNormals( Normal* normals )
{
	gEng.PutCacheNormals( &mCacheOpqNormals, normals );
}

/*****************************************************************************
 * Transform world --> eye.
 *****************************************************************************/
const EyeVertex*
ObjectMesh::GetEyeVertexs( void )
{
	const uint         vcnt = GetVertexCnt();
	const WorldVertex* wv   = GetWorldVertexs();
	EyeVertex*         ev   = gEng.mTempArrayEyeVertex.Get( vcnt );
	TRANSFORM_VERTEX( ev, wv, vcnt );
	return ev;
}

////////////////////////////////////////////////////////////////////////////////
//////////////////////////  Object normal vectors  /////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Get the normal vectors of an object.
 *****************************************************************************/
Normal*
ObjectMesh::GetWorldNormals( void )
{
	// Are the normal vectors of this object in the cache?
	Normal* normals = GetCacheNormals();
	if ( normals == NULL )
	{
		//ENGINE_STATS_CODE( ++gEng.mStats.mCacheMissNormals; )	// done at Engine::GetCacheNormals()

		// Not cached, recalculate them.
		// New array of normal vectors.
		const uint normalCnt = GetNormalCnt();
ASSERT( normalCnt > 0 );
		normals = new Normal[normalCnt+2];								// extra to be safe
INVALIDATE_TYPESIG( &normals[normalCnt], TYPESIG_LIB_NORMAL );	// hopefully catch overrun
		CalcNormals( normals );

		// Put them into cache.
		PutCacheNormals( normals );
		DEBUG_VERBOSE_CODE( *gStreamDebug << "Caching normal vectors [obj=" << GetName() << "] \n"; )
	}
#if DEBUG
	else { DEBUG_VERBOSE_CODE( *gStreamDebug << "Using cached normal vectors [obj=" << GetName() << "] \n"; ) }
#endif
	return normals;
}

/*****************************************************************************
 * Calculate the normal vectors on every polygon of this 3D object.
 * @return An array of Normal structs.
 * The count of elements can be found by calling Object::GetPolygonCnt()
 * as one normal vector is associated with one polygon.
 *****************************************************************************/
void
ObjectMesh::CalcNormals( Normal* normals/*OUT*/ )
{
	// Get object's world vertexs.
	const WorldVertex* wv = GetWorldVertexs();
#if DEBUG
	const uint polygonCnt = GetPolygonCnt();
#endif
CHECK_TYPESIG( wv, TYPESIG_LIB_VERTEX_WORLD );
ASSERT( polygonCnt > 0 );

	// For each polygon of this Object, calculate a normal vector.
	uint i = 0;
	const Dlist<Polygon>& dlist = GetPolygonList().GetDlist();
	for ( const Dlink<Polygon>* dlink = dlist.ForStart();
          dlist.ForTest(dlink);
          dlist.ForNext(dlink) )
	{
		// Get Polygon object.
		const Polygon* const polygon = dlink->mObj;
CHECK_TYPESIG( polygon, TYPESIG_POLYGON );

		// Get indexes of the 3 vertexes on the next polygon.
		// 3 vertexes are needed to calculate a normal vector.
ASSERT( polygon->GetVixCnt() >= 3 );
		const Vix* vixArr = polygon->GetVixArr();

		// Calculate a normal vector on this polygon.
		// The resulting normal vector exists in a normal coordinate system.
CHECK_TYPESIG( &wv[ vixArr[0] ], TYPESIG_LIB_VERTEX_WORLD );
CHECK_TYPESIG( &normals[i], TYPESIG_LIB_NORMAL );
		CrossProduct( normals[i].mNorm,
	                  wv[ vixArr[0] ],			// vertex[0] of polygon (origin of normal vector)
	                  wv[ vixArr[1] ],			// vertex[1] of polygon
	                  wv[ vixArr[2] ] );		// vertex[2] of polygon
		normals[i].mVixNormOrig = vixArr[0];	// set origin of normal vector

ASSERT( i < polygonCnt );
		++i;
	}
ASSERT( i == polygonCnt );
}

////////////////////////////////////////////////////////////////////////////////
/////////////////////////  ObjectPolymorph class  //////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Constructor.
 *****************************************************************************/
ObjectPolymorph::ObjectPolymorph( QuadrantPtrConst q )
	: Parent(q)
{
	SetLodObj( eLodObj_MIN );
	DEBUG_CODE( CheckObjectCtor(); )
}
ObjectPolymorph::~ObjectPolymorph()
{
	DEBUG_CODE( CheckObjectDtor(); )
}

/*****************************************************************************
 * ObjectPoly cache methods.
 * To save memory, the cache opaque structure of the base Object class
 * is used for array[0] as the array is minus.
 *****************************************************************************/
WorldVertex*
ObjectPolymorph::GetCacheWorldVertex( void )
{
	if ( mLod == 0 )
		return Parent::GetCacheWorldVertex();
	else
		return gEng.GetCacheWorldVertex( &mCacheOpqWorldVertexArr[mLod-1] );
}
void
ObjectPolymorph::PutCacheWorldVertex( WorldVertex* wv )
{
	if ( mLod == 0 )
		Parent::PutCacheWorldVertex( wv );
	else
		gEng.PutCacheWorldVertex( &mCacheOpqWorldVertexArr[mLod-1], wv );
}
Normal*
ObjectPolymorph::GetCacheNormals( void )
{
	if ( mLod == 0 )
		return Parent::GetCacheNormals();
	else
		return gEng.GetCacheNormals( &mCacheOpqNormalsArr[mLod-1] );
}
void
ObjectPolymorph::PutCacheNormals( Normal* normals )
{
	if ( mLod == 0 )
		Parent::PutCacheNormals( normals );
	else
		gEng.PutCacheNormals( &mCacheOpqNormalsArr[mLod-1], normals );
}

/*****************************************************************************
 * ObjectPoly PolygonList methods.
 * To save memory, the Object member is used for array[0] as the array is minus.
 *****************************************************************************/
PolygonList&
ObjectPolymorph::GetPolygonList( eLodObj lod )
{
	ASSERT( lod < eLodObj_LEVELS );
	if ( lod == 0 )
		return Parent::GetPolygonList();
	else
		return mPolygonListArr[lod-1];
}
PolygonList&
ObjectPolymorph::GetPolygonList( void )
{
	return GetPolygonList( mLod );
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////  Special class  ///////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Special constructor.
 *****************************************************************************/
Special::Special( QuadrantPtrConst q )
	: Parent(q)
{
	// Let position be same as quadrant (doesn't really matter).
	SetPosition( q->GetPosition() );
}

////////////////////////////////////////////////////////////////////////////////
//////////////////////////////  Omni class  ////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Omni ctor/dtor.
 *****************************************************************************/
Omni::Omni( QuadrantPtrConst q )
:   Special(q)
{
	FinalConstruction();
}
Omni::~Omni()
{
	// NOP
}

/*****************************************************************************
 * @return Distance between object and viewplane.
 *****************************************************************************/
fp
Omni::GetDistance( void )
{
	return gWorld.DistanceOutside();
}

/*****************************************************************************
 * Support for drawing an Omni once per frame.
 *****************************************************************************/
bool
Omni::IfOmniDrawnThisFrame( void )
{
	// Get/SetFrameDrawn() are pure virtual methods which access
	// a static member of a Omni-derived class (Sky or Terrain eg).
	// So each particular Omni-derived class is drawn once per frame (per view).
	// TODO per-view
	const uint frame = gEng.mView.GetViewFrameCnt();
	if ( GetFrameDrawn() == frame )
	{
		// Was drawn this frame.
		return true;
	}
	else
	{
		// First time drawn for this frame.  Remember the frame number.
		SetFrameDrawn( gEng.mView.GetViewFrameCnt() );
		return false;
	}
}

////////////////////////////////////////////////////////////////////////////////
//////////////////////////  ObjectGroup class  /////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Constructor.
 *****************************************************************************/
ObjectGroup::ObjectGroup( QuadrantPtrConst q, bool reproducible )
	: Special(q)
{
	mBits.mCubeValid	= false;		// to be assigned when/if first Object is added
	mBits.mReproducible	= reproducible;
	FinalConstruction();
	DEBUG_CODE( CheckObjectCtor(); )
}
ObjectGroup::~ObjectGroup()
{
	DEBUG_CODE( CheckObjectDtor(); )
}

/*****************************************************************************
 * Add object to group.
 *****************************************************************************/
void
ObjectGroup::AddObject( Object* obj )
{
	Posrad posradObj( obj->GetPosition(), obj->GetRadius() );

	// Initialize cube with position of the first object.
	if ( ! mBits.mCubeValid )
	{
		mCube.Set( posradObj );
		mBits.mCubeValid = true;
	}

	// Append object to list.
	mObjectDlist.Append( obj );

	// Move the object into the shadow quadrant (outside normal rendering).
	GetQuadrant()->GetShadow()->Take( *obj, obj->GetQuadrant() );

	// Enclose the object.
	mCube.Grow( posradObj );

	// Here's why the usually-required FinalConstruction() is N/A.
	// For this ObjectGroup, update its position,radius which is used
	// for the visibility tests done at Object::Visible().
	const Position posGroup = mCube.GetCenter();
	SetPosition( posGroup );
	SetRadius( mCube.GetRadius() * 1.2 );
}

/*****************************************************************************
 * Visible?
 *****************************************************************************/
int
ObjectGroup::Visible( void )
{
	int vis = Parent::Visible();
	ENGINE_STATS_CODE( if (  vis ) ++gEng.mStats.mObjectGroupVisible; )
	ENGINE_STATS_CODE( if ( !vis ) ++gEng.mStats.mObjectGroupInvisible; )
	return vis;
}

/*****************************************************************************
 * Draw objects in group.
 *****************************************************************************/
void
ObjectGroup::Draw( void )
{
	ASSERT2( ! IfEmpty() );  // for speed, empty ObjectGroups should be deleted
	DrawObjectList<Object>( mObjectDlist );
}
