/*****************************************************************************
 * $Id: eng_object.hh,v 1.174 2006/11/25 18:17:47 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Object and associated classes.
 *//*
 * LEGAL:	COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#ifndef ENG_OBJECT_HH
#define ENG_OBJECT_HH 1

#include "gfx_list.hh"
#include "gfx_cache.hh"
#include "eng_polygon.hh"
#include "eng_collision.hh"
#include "eng_filter.hh"

class Quadrant;
#if DEBUG
typedef       SafePtr<Quadrant> QuadrantPtr;
typedef const SafePtr<Quadrant> QuadrantPtrConst;
#else
typedef Quadrant*       QuadrantPtr;
typedef Quadrant* const QuadrantPtrConst;
#endif
class Dyna;
class Detector;

////////////////////////////////////////////////////////////////////////////////
/// @brief The base 3D Object class (pure abstract class, no data members).
///
/// @verbatim
/// Get/Set() methods are provide to access data members whose values may be
/// dynamic or whose storage may vary.  Eg, the position of a Dyna object is
/// dynamic and stored in its matrix, but the position of a Fixed object is stored
/// in its mPosition member.
///
/// To conserve memory (which is necessary due to the multitude of objects):
/// - Avoid adding more per-instance data members.
/// - Add new data members as static vars if possible.  Static members will be
///   shared by all objects but this can be made to work by indirectly accessing
///   the static var thru a virtual function that sets it appropriately for
///	  a particular object.
///
/// Destructor notes:
/// - ~CacheOpq() will automatically free cached objects.
/// - Asymmetrically, only some derived classes construct a Detector,
///   but all should let ~Object() delete it.
///
/// Usage:
/// - See comments in eng.cc for how the render loop affects Draw() methods.
///
/// "local normals" is written to distinguish from the "world normals".
/// Local normals are invariant and specified by an imported 3D model.
/// As a Dyna is transformed, "local normals" are transformed into "world normals".
/// Nevertheless, normal vectors coordinates are always in normal space.
/// @endverbatim
///
class Object
{
PREVENT_COPYING(Object)
protected:							Object( void ) { SET_TYPESIG(this,TYPESIG_OBJECT); }
public:     virtual					~Object()      { CHECK_TYPESIG(this,TYPESIG_OBJECT);
                                                     INVALIDATE_TYPESIG(this,TYPESIG_OBJECT); }
public:     virtual void			Init( void ) { }	///< can be used to relocate/reset an object, keep base members unchanged
protected:	virtual void			FinalConstruction( void ) = 0;	///< to be called by constructor of final derived class
public:     virtual void			Reset( void ) { }	///< to reset view (doesn't fully initialize)
public:     void					SelfDestruct( void );
public:		virtual const string	GetName( void ) = 0;
public:     virtual bool			Reproducible( void ) = 0;
public:     virtual void			Draw( void ) = 0;
public:		static void				StartFrame( void ) { }
/// Dyna position is in extended-precision matrix but Fixed is in a Position member.
public:     virtual Position		GetPosition( void ) = 0;
public:     virtual void			SetPosition( const Position& pos ) = 0;
public:     virtual QuadrantPtrConst GetQuadrant( void ) = 0;
public:     virtual void			SetQuadrant( QuadrantPtrConst q ) = 0; // without telling previous Quadrant
public:		virtual Dlink<Object>*	GetLink( void ) = 0;
public:		virtual void			SetLink( Dlink<Object>* linkObject ) = 0;  // NULL link may be passed
public:		virtual fp				GetRadius( void ) const = 0;  // freq
public:     virtual void			SetRadius( fp radius ) = 0;
protected:	void					CalcRadiusAdjustPosition( int vcnt, const WorldVertex* wv, Cube<fp>& cube, bool adjustPosition = true );
protected:	void					CalcRadius( int vcnt, const WorldVertex* wv, Cube<fp>& cube );
protected:	void					CalcRadius( int vcnt, const WorldVertex* wv ) { static Cube<fp> cube; CalcRadius(vcnt,wv,cube); }
public:		virtual fp				GetDistance( void );
protected:	fp						GetDistanceFromPosition( void );  // limited use (ignores radius)
public:		virtual void			SetLodObj( eLodObj lod ) { }
/// Visible*() usually returns a computed pixel width.
public:     enum { INVISIBLE = 0, VISIBLE_DEFAULT = 100 };  ///< if Visible() didn't compute pixel width
public: 	virtual int				Visible( void );		///< defined in eng_filter.cc
protected:	fp						VisibleDistance( void );
private:	virtual void			MoveDetector( QuadrantPtrConst giver, QuadrantPtrConst taker ) { }
#if DEBUG
protected:	enum { MAX_OBJECTS = 30*1000*1000 };  // max LOD normal produces a few million Objects
protected:	virtual void CheckObjectCtor( void ) { }
protected:	virtual void CheckObjectDtor( void ) { }
#endif // DEBUG
friend class Quadrant;
public:     DECL_TYPESIG( TYPESIG_OBJECT )
};
#if DEBUG
typedef       SafePtr<Object> ObjectPtr;
typedef const SafePtr<Object> ObjectPtrConst;
#else
typedef Object* ObjectPtr;
typedef Object* ObjectPtrConst;
#endif

////////////////////////////////////////////////////////////////////////////////
/// @brief Concrete Object class (has data members).
///
class ObjectData : public virtual Object
{
PREVENT_COPYING(ObjectData)
private: typedef Object Parent;
public:		explicit			ObjectData( QuadrantPtrConst q );
public:     virtual				~ObjectData();			///< see destructor notes below

public:     QuadrantPtrConst   	GetQuadrant( void )            { return mQuadrant; }
public:     void				SetQuadrant( QuadrantPtrConst q ) { mQuadrant = q; } // without telling previous Quadrant
private:	QuadrantPtr     	mQuadrant;              ///< which quadrant object resides in

public:		fp					GetRadius( void ) const { return mRadius; }
public:     void				SetRadius( fp radius ) { mRadius = radius; }
private:    fp					mRadius;				///< used for visibility testing, see CalcRadius() and Visible()

public:		Dlink<Object>*		GetLink( void )
                                {
                                    CHECK_TYPESIG(mLinkObject,TYPESIG_LIB_DLINK);
                                    return mLinkObject;
                                }
public:		void				SetLink( Dlink<Object>* linkObject )  // allow dtors to pass NULL links
                                {
                                    CHECK_TYPESIG_NULL_OK(linkObject,TYPESIG_LIB_DLINK);
                                    mLinkObject = linkObject;
                                }
protected:  Dlink<Object>*		mLinkObject;            ///< this object's link in Quadrant's Dlist of Objects

#if DEBUG
protected:	virtual void CheckObjectCtor( void );
protected:	virtual void CheckObjectDtor( void );
protected:	static int mInstanceCnt;
#endif // DEBUG
};

////////////////////////////////////////////////////////////////////////////////
/// @brief Objects with a mesh.
/// @remarks Use GetPolygonList() instead of mPolygonList.  GetPolygonList() is a
/// virtual function to let ObjectPoly override it for multiple polygon lists.
/// The support for normal calculations was moved out of the base Object class
/// into this class which a container for polygons from which normals are calculated.
///
class ObjectMesh : public ObjectData
{
PREVENT_COPYING(ObjectMesh)
private: typedef ObjectData Parent;
public:		explicit				ObjectMesh( QuadrantPtrConst q );
public:     virtual					~ObjectMesh();
/// GetWorldVertexs() must return a calculated result before FinalConstruction() executes,
/// and never a pointer to an uncalculated array of WorldVertex.
public:     virtual uint				GetVertexCnt( void ) = 0;
public:     virtual const WorldVertex*	GetWorldVertexs( void ) = 0;
public:		virtual const EyeVertex*	GetEyeVertexs( void );
public:		virtual void				SetVertexCnt( uint vcnt ) = 0;			// for ObjectPython class only
public:		virtual void				SetWorldVertexs( WorldVertex* wv ) = 0; // for ObjectPython class only
public:		virtual PolygonList&	GetPolygonList( void ) { return mPolygonList; }  // see comments
public:     virtual uint			GetPolygonCnt( void )  { return GetPolygonList().GetPolygonCnt(); }
public:		uint					GetNormalCnt( void ) { return GetPolygonCnt(); } // equals
public:     virtual Normal*			GetWorldNormals( void );
protected:	void					CalcNormals( Normal* normals/*OUT*/ );
// Object::Visible() requires pre-calculating the radius and the position
// of an object to be adjusted to its center (relative to its world vertexs).
// But some objects are constructed with a position not quite at its center
// (eg, the position is initially a corner or calculated before the center is known).
// Hence the requirement for calling FinalConstruction() which should call
// CalcRadiusAdjustPosition() (unless radius/position are already correct).
// ObjectMesh::CalcRadius*() require a mesh but Object::CalcRadius*() don't.
protected:	void					CalcRadius( void ); 
protected:	void					CalcRadius( int vcnt, const WorldVertex* wv, Cube<fp>& cube )
									{ Object::CalcRadius( vcnt, wv, cube ); }
protected:	void					CalcRadius( int vcnt, const WorldVertex* wv )
									{ Object::CalcRadius( vcnt, wv ); }
protected:	void					CalcRadiusAdjustPosition( int vcnt, const WorldVertex* wv, Cube<fp>& cube, bool adjustPosition = true )
                                    { Object::CalcRadiusAdjustPosition( vcnt, wv, cube, adjustPosition ); }
protected:	void					CalcRadiusAdjustPosition( Cube<fp>& cube );
protected:	void					CalcRadiusAdjustPosition( void );
protected:	int						VisibleSimple( void );	///< for special case Objects
// Cache becomes specific to a LOD when overridden by the ObjectPolymorph class.
protected:	virtual WorldVertex*	GetCacheWorldVertex( void );
protected:	virtual void			PutCacheWorldVertex( WorldVertex* wv );
protected:	virtual Normal*			GetCacheNormals( void );
protected:	virtual void			PutCacheNormals( Normal* normals );
private:	CacheOpq<WorldVertex>	mCacheOpqWorldVertex;	///< don't use directly, use methods
private:    CacheOpq<Normal>		mCacheOpqNormals;		///< don't use directly, use methods
private:	PolygonList				mPolygonList;			///< don't use directly, use methods
// Collided() does collision-detection tests.
public: 	bool					Collided( Dyna& dyna ) { if ( mLinkDetector != NULL ) return mLinkDetector->mObj->Collided( dyna ); else return false; }
// An object that has a detector can be made indestructible by EnableDetector(false).
// Most Objects don't have Detector objects.
private:	void					MoveDetector( QuadrantPtrConst giver, QuadrantPtrConst taker );  // at eng_world.cc
public:		void					EnableDetector( bool flag ) { if ( mLinkDetector ) mLinkDetector->mObj->Enable( flag ); }
public:		void					SetDetector( Dlink<Detector>* linkDetector ) { mLinkDetector = linkDetector; }
public:		bool					IfDetectorEnabled( void ) { return mLinkDetector ? mLinkDetector->mObj->IfEnabled() : false; }
protected:	Dlink<Detector>*		mLinkDetector;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief Polymorphic objects which have different meshes for each LOD.
/// @remarks To avoid confusion, LodObj and LodUser are written explicitly rather than simply Lod.
/// Both ObjectPoly and DynaModel have multiple polygon lists, but the ObjectPoly
/// generates its polygon lists while DynaModel imports its polygon lists.
///
class ObjectPolymorph : public ObjectMesh
{
PREVENT_COPYING(ObjectPolymorph)
private: typedef ObjectMesh Parent;
public:		explicit		ObjectPolymorph( QuadrantPtrConst q );
public:		virtual			~ObjectPolymorph();
protected:	uint			GetLodObj( void )        { ASSERT( mLod < eLodObj_LEVELS ); return mLod; }
public:		void			SetLodObj( eLodObj lod ) { ASSERT( lod  < eLodObj_LEVELS ); mLod = lod; }
public:		WorldVertex*	GetCacheWorldVertex( void );
public:		void			PutCacheWorldVertex( WorldVertex* wv );
public:		Normal*			GetCacheNormals( void );
public:		void			PutCacheNormals( Normal* normals );
public:		PolygonList&	GetPolygonList( void );
protected:	PolygonList&	GetPolygonList( eLodObj lod );
             // ObjectMesh::GetPolygonCnt() suffices
private:	eLodObj					mLod;
private:	CacheOpq<WorldVertex>	mCacheOpqWorldVertexArr[eLodObj_LEVELS-1];	// minus 1 to save memory,
private:    CacheOpq<Normal>		mCacheOpqNormalsArr[eLodObj_LEVELS-1];		// use Object mCacheOpq as [0].
private:	PolygonList				mPolygonListArr[eLodObj_LEVELS-1];
};

////////////////////////////////////////////////////////////////////////////////
/// @brief For specially-rendered or top-level objects.
///
class Special : public ObjectData
{
PREVENT_COPYING(Special)
private:
	typedef ObjectData Parent;

public:
	explicit				Special( QuadrantPtrConst q );
	virtual					~Special() { }
	virtual Position		GetPosition( void ) { return mPos; }
	virtual void			SetPosition( const Position& pos ) { mPos = pos; }

protected:
	Position	mPos;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief For objects that are always rendered (flat land, sky cylinder).
///
/// Omnis are linked into a separate Quadrant (speeds sorting objects).
/// Omni implements some of ObjectData's interface [Link()] but is derived from Special.
///
class Omni : public Special
{
PREVENT_COPYING(Omni)
private:
	typedef Special Parent;

public:
	explicit                Omni( QuadrantPtrConst q );
    virtual                 ~Omni();
 	virtual int             Visible( void ) { return VISIBLE_DEFAULT; }
	virtual fp              GetDistance( void );
	virtual void            FinalConstruction( void ) { }
	// Support for drawing an Omni once per frame.
	virtual bool			IfOmniDrawnThisFrame( void );
	virtual uint			GetFrameDrawn( void ) = 0;  // intended to access a static uint of derived class
	virtual void			SetFrameDrawn( uint frame ) = 0;
};

////////////////////////////////////////////////////////////////////////////////
/// @brief Template class for stationary Fixed objects.
/// Actually, the position of Fixed objects can be changed, thought most are stationary.
/// Use the typedefs FixedData, FixedMesh, FixedPolymorph (next section).
///
template<typename OBJECT>
class Fixed : public OBJECT
{
PREVENT_COPYING(Fixed<OBJECT>)
private:
	typedef OBJECT Parent;

public:
	Fixed( QuadrantPtrConst q, const Position& pos ) : OBJECT(q)
	{
		SET_TYPESIG(this,TYPESIG_FIXED);
		SetPosition( pos );
	}
	virtual ~Fixed()
	{
		INVALIDATE_TYPESIG( this, TYPESIG_FIXED );
	}
	virtual bool Reproducible( void )
	{
		return true;
	}
	virtual Position GetPosition( void )
	{
		return mPos;
	}
	virtual void SetPosition( const Position& pos )
	{
		mPos = pos;
	}

protected:
	Position		mPos;

public:
	DECL_TYPESIG( TYPESIG_FIXED )
};

////////////////////////////////////////////////////////////////////////////////
/// @brief Fixed class typedefs.

// Lightweight Fixed object.
typedef Fixed<ObjectData> FixedData;

// Fixed object with a mesh.
typedef Fixed<ObjectMesh> FixedMesh;

// Fixed object with multiple meshes (polygon lists).
typedef Fixed<ObjectPolymorph> FixedPolymorph;

////////////////////////////////////////////////////////////////////////////////
/// @brief Container of adjacent objects.
/// @verbatim
/// The purpose is to reduce visibility tests by testing the visibility of ObjectGroup.
/// If ObjectGroup isn't visible, all of its objects are considered not visible.
/// Otherwise, visibility tests of individual objects are still done (parts
/// of ObjectGroup may or may not be visible).
/// ObjectGroup's mCube is grown to enclose added Objects.
/// Object::Visible() calls GetPosition(),GetRadius() whose values are derived from mCube.
///
/// Shadow quadrants:
/// ObjectGroup is placed into an actual Quadrant.
/// But its Objects are placed into the actual Quadrant's shadow.
/// Shadow Quadrants are outside the render loop, and are needed as a placeholder
/// as the Object interface requires an Object to have a Quadrant.
/// When Quadrant::GetObjectCnt() is called, it includes the shadow's count (if it exists).
///
/// For speed, if an ObjectGroup is constructed but it actually has no objects,
/// then delete it.
/// @endverbatim
///
class ObjectGroup : public Special
{
PREVENT_COPYING(ObjectGroup)
private:
	typedef Special Parent;

public:
					ObjectGroup( QuadrantPtrConst q, bool reproducible = true );
	virtual			~ObjectGroup();
	void			AddObject( Object* obj );
	void			Draw( void );
	// FinalConstruction() is a NOP because construction continues
	// with AddObject() which updates radius/position.
	void			FinalConstruction( void ) { }
	bool			Reproducible( void ) { return mBits.mReproducible; }
	const string	GetName( void ) { return string("ObjectGroup"); }
	bool			IfEmpty( void ) const { return mObjectDlist.IfEmpty(); }
	int				Visible( void );

private:
    struct ObjectGroupBits
    {
		uint mCubeValid		: 1;
		uint mReproducible	: 1;
	};
	ObjectGroupBits		mBits;
	Dlist<Object>		mObjectDlist;	///< list of contained Objects
	Cube<fp>			mCube;			///< encloses every added Object
};

////////////////////////////////////////////////////////////////////////////////
/// @brief Template class with methods that store extended info from
/// visibility tests during the object-sorting (pre-rendering) phase.
/// When Objects are sorted, the return value of Visible() (a width estimation)
/// isn't stored as most Objects only need to know if they're visible or not.
///
template<typename OBJECT>
class ObjectVisExt : public OBJECT
{
PREVENT_COPYING(ObjectVisExt)
private:
	typedef OBJECT Parent;

public:
	ObjectVisExt( QuadrantPtrConst q, const Position& pos ) : OBJECT(q,pos) { }
	virtual ~ObjectVisExt() { }

	/// Overridden method, which is called during object sorting,
	/// that stores visibility test info.
	virtual int Visible( void )
	{
		mVisibleStored = Parent::Visible();
		if ( mVisibleStored )
			mVisibleDistanceStored = Parent::VisibleDistance();
		else
			mVisibleDistanceStored = FLOAT_LARGE;  // not visible, default to extreme distance
		return mVisibleStored;
	}

	/// @return Stored result of Visible() computed prior at object-sort phase.
	int VisibleStored( void )
	{
		return mVisibleStored;
	}

	/// @pre Don't call if VisibleStored() returned zero!
	/// @return Stored result of VisibleDistance() computed prior at object-sort phase.
	fp VisibleDistanceStored( void )
	{
		return mVisibleDistanceStored;
	}

private:
	int		mVisibleStored;
	fp		mVisibleDistanceStored;
};

#endif // ENG_OBJECT_HH
