/*****************************************************************************
 * $Id: eng_object.cc,v 1.177 2006/08/10 03:18:58 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( QuadrantPtrc q ) 
{
	SET_TYPESIG( this, TYPESIG_OBJECT );

	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 );

	INVALIDATE_TYPESIG( this, TYPESIG_OBJECT );
	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 ) 
{
	// Add to zombie list.
	// The reason for the zombie list is that C++ objects can't delete themselves.
	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^2 from object to viewpoint.
 *****************************************************************************/
fp
Object::Distance2( void )
{
	Position posEye = gEng.mEye.GetPosition();
	Position pos    = GetPosition();
	fp xd = pos.x - posEye.x;
	fp yd = pos.y - posEye.y;
	fp zd = pos.z - posEye.z;
	return SQUARE(xd) + SQUARE(yd) + SQUARE(zd);
}

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

/*****************************************************************************
 * Object constructor.
 *****************************************************************************/
ObjectMesh::ObjectMesh( QuadrantPtrc 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( QuadrantPtrc 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( QuadrantPtrc q )
	: Parent(q)
{
	// Let position be same as quadrant (doesn't really matter).
	SetPosition( q->GetPosition() );
}

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

/*****************************************************************************
 * Omni constructor.
 *****************************************************************************/
Omni::Omni( QuadrantPtrc q )
	: Special(q)
{
	// Omni objects (which are always-rendered objects such as the sky cyliner)
	// are linked into a separate list of a quadrant in order to be drawn
	// if the quadrant isn't facing.
	mLinkOmni = GetQuadrant()->AddOmni( this );
	FinalConstruction();
	DEBUG_CODE( CheckObjectCtor(); )
}
Omni::~Omni()
{
	CHECK_TYPESIG( GetQuadrant(), TYPESIG_QUADRANT );
	GetQuadrant()->RemoveOmni( mLinkOmni );
	mLinkOmni = NULL;
	DEBUG_CODE( CheckObjectDtor(); )
}

/*****************************************************************************
 * Return distance^2.
 *****************************************************************************/
fp
Omni::Distance2( void )
{
	fp d = gWorld.DistanceOutside();
	return SQUARE(d);
}

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

/*****************************************************************************
 * Constructor.
 *****************************************************************************/
ObjectGroup::ObjectGroup( QuadrantPtrc 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 );
}
