/*****************************************************************************
 * $Id: eng_world.hh,v 1.98 2006/08/10 03:18:58 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	World, Quadrant, Locus classes.
 * @remarks	A Position object is 36 bytes on x86 so avoid returning by value.
 *//*
 * LEGAL:	COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#ifndef ENG_WORLD_HH
#define ENG_WORLD_HH 1

class Quadrant;  // eng_object.hh has QuadrantPtr typedef
class Locus;
class QuadrantObject;
typedef void (*ForEachObjectCallback)( Object* obj );
typedef vector<Quadrant*>::iterator QuadrantItr;
typedef vector<Quadrant*>::const_iterator ConstQuadrantItr;

////////////////////////////////////////////////////////////////////////////////
/// @class World
/// @brief World class defines the simulated world.  It consists of quadrants.
/// @remarks The world's origin is always at (0,0,0) which simplifies code.
/// This base class doesn't know how to make the world nor populate it with Objects.
class World
{
PREVENT_COPYING(World)
public:
						World( void );		// substituted by Make()
	virtual				~World() { }		// substituted by Destroy()
	virtual void		Make( void ) = 0;
protected:
	void				Make1( const Array<3>& dimQuadrant, const fp quadrantEdge, const fp margin, const Array<3>& dimLocus );
	void				Make2( void );
public:
	void				Destroy( void );
	bool				IfMade( void ) { return mMade; }
	virtual void		PopulateQuadrant( QuadrantPtrc q ) = 0;
	void				ForEachObject( ForEachObjectCallback callback );
	virtual bool		Fence( Dyna& dyna, Position& pos /*IN/OUT*/ );
	const Array<3>&		GetDimQuadrant( void ) { return mDimQuadrant; }
	uint				GetQuadrantCnt( void ) { return mQuadrants.size(); }
	fp					GetQuadrantEdge( void ) { return mQuadrantEdge; }
	uint				GetQuadrantIdx( const Array<3>& tuple );
	uint				GetQuadrantIdx( const Position& pos );
	QuadrantPtrc 		GetQuadrant( const Array<3>& tuple ) { return mQuadrants[ GetQuadrantIdx(tuple) ]; }
	QuadrantPtrc     	GetQuadrant( const Position& pos ) { return mQuadrants[ GetQuadrantIdx(pos) ]; }
	const Array<3>&		GetQuadrantTuple( const Position& pos );
	QuadrantPtrc     	GetCenterQuadrant( void );
	Position			GetCenter( void ) const { ASSERT(mDimValid); return mCenter; }
	bool				IfCenterQuadrant( QuadrantPtrc q );
	bool				IfEdgeQuadrant( QuadrantPtrc q );
	uint				GetObjectCnt( void ) { ASSERT(mObjectCnt >= 0); return mObjectCnt; }
	void				IncObjectCnt( void ) { ++mObjectCnt; ++mObjectCntMade; }
	void				DecObjectCnt( void ) { --mObjectCnt; ASSERT(mObjectCnt >= 0); }
	uint				GetObjectCntInCenterQuadrant( void ) const;
	virtual bool		IfSmallObject( const Object& obj ) const = 0;
	void				SetObjectSmallDist ( fp dist );
	void				SetObjectLargeDist( fp dist );
	fp					DistanceOutside( void );
	uint				CacheSize( void );
	void				PreloadCaches( void );
	bool				IfBelowGround( const Position& pos );
	bool				IfBelowGround( const MatrixExt& m );
						/// GetGroundY() returns the Y coordinate of the terrain at a 3D point.
	fp					GetGroundY( const Position& pos )   { return WORLD(2.0); } // TODO consult terrain map

protected:
	Array<3>			WrapQuadrant( const Array<3>& tuple );

	bool				mMade;
	uint				mObjectCnt;			///< decremented when an Object is deleted
	uint				mObjectCntMade;		///< never decremented

	// Define the world dimensions:
	fp					mQuadrantEdge;		///< width of quadrant edge (all are equal)
	fp					mMargin;			///< for when Fence() wraps a Dyna
	Array<3>			mDimQuadrant;		///< dimensions of the world (in quadrants)
	Array<3,fp>			mDimWorld;			///< dimensions of the world (in world coords)
	Array<3>			mDimLocus;
	Position			mCenter;			///< center of the center quadrant
	bool				mDimValid;			///< also applies to mQuadrantEdge, mCenter

public:
	vector<Quadrant*>	mQuadrants;			///< Quadrants are divisions of the World
	Locus*				mLocus;				///< locus of Quadrants being rendered

	fp					mObjectSmallDist;	///< used by Object::Visible()
	fp					mObjectLargeDist;	///< max distances small/large objects are visible
	fp					mObjectSmallDist2;	///< squared
	fp					mObjectLargeDist2;	///< squared
};

////////////////////////////////////////////////////////////////////////////////
/// @class Quadrant
/// @brief A quadrant is a grid of the simulated world containing a set of objects.
enum eQuadrantState { eQuadrantState_EMPTY, eQuadrantState_SHRUNK, eQuadrantState_POPULATED };
class Quadrant
{
PREVENT_COPYING(Quadrant)
public:
						Quadrant( const Array<3>& tuple,
                                  const fp edge,
                                  const uint seed,
                                  bool shadow = false );
						~Quadrant();
	// Quadrants states:
	// empty		Has zero objects.
	// populated	Has reproducible and non-reproducible objects
	// shrunk		Has non-reproducible objects
	eQuadrantState		GetState( void ) const	         { return mState; }
	void				SetState( eQuadrantState state ) { mState = state; }
	void				Populate( void );
	void				Shrink( void );
	void				Take( Object& object, QuadrantPtrc giver );
	const Array<3>&		GetTuple( void ) const { return mTuple; }
	const Cube<fp>&		GetCube( void ) const { return mCube; }
	uint				GetSeed( void ) const { return mSeed; }
	QuadrantPtrc     	GetShadow( void ) const { return mShadow; }
	uint				GetObjectCnt( void ) const;
	Position			GetPosition( void ) const { return mPosition; }
	Position			GetCenter( void ) const { return mCenter; }
	void				RandomPosition( Position& pos ) const;
	Dlink<Object>*		AddObject( Object* obj );           // not Ptr<>
	void				RemoveObject( Dlink<Object>* dlink );
	Dlink<Omni>*		AddOmni( Omni* omni );              // not Ptr<>
	void				RemoveOmni( Dlink<Omni>* dlink );
	Dlink<Detector>*	AddDetector( Detector* detector );  // not Ptr<>
	void				RemoveDetector( Dlink<Detector>* dlink );
	Dlist<Object>&      GetObjectDlist( void )	 { return mObjectDlist; }
	Dlist<Detector>&	GetDetectorDlist( void ) { return mDetectorDlist; }
	Dlist<Omni>&        GetOmniDlist( void )	 { return mOmniDlist; }
	void				DynaMoved( Dyna& dyna );
	void				DynaMoved( Dyna& dyna, const Position& pos );
						// Don't use IfInside(Posrad) for Fixed objects at ground-level
						// for (n,1,n) Worlds as the radius will extend below ground.
	bool				IfInside( const Position& pos ) const;
	bool				IfInside( const Posrad& posrad ) const;
	bool				IfVisible( void ) const;

protected:
	eQuadrantState		mState;
	static fp			mEdge;
	Quadrant*			mShadow;		///< holds Object managed by ObjectGroup
	uint				mSeed;			///< random seed
	Array<3>			mTuple;			///< for mQuadrants[x,y,z]
	Cube<fp>			mCube;			///< defines the quadrant in world space
	Position			mPosition;		///< origin, equals Cube::x1,y1,z1
	Position			mCenter;		///< center of Quadrant
	uint				mObjectCnt;		///< amount of objects in this quadrant
	Dlist<Object>		mObjectDlist;	///< list of objects in the quadrant
	Dlist<Omni>			mOmniDlist;		///< special objects such as Terrain/Sky
    // Maintainting a Detector list is an optimization vs. calling every
    // Object's Collided() method as mosts Objects don't have Detectors
	Dlist<Detector>		mDetectorDlist;	///< Detector objects of Dynas migrate to other quadrants

private:
	Ptr<QuadrantObject>	mQuadrantObject; ///< to speed visibility test

public:
	DECL_TYPESIG( TYPESIG_QUADRANT )

friend class Locus;
friend ostream& operator<<( ostream& strm, const Quadrant& o );
};

////////////////////////////////////////////////////////////////////////////////
/// @class Locus
/// @brief The locus is subset of quadrants that are being rendered.
/// @remarks Each dimension must be an odd value.  The reason is that
/// the center of the locus is computed by dividing by 2.  For a 3x3x3 locus,
/// its center is 1x1x1.  A 4x4x4 locus would unbalanced with a center of 2x2x2.
class Locus
{
PREVENT_COPYING(Locus)
public:				// Dimensions must have odd values.
	explicit		Locus( const Array<3>& dim );
                    ~Locus();
	void			Update( const Position& pos );
	void			Update( void );
	uint			GetQuadrantIdx( const Array<3>& tuple ) const;
	QuadrantPtrc 	GetCenterQuadrant( void ) const;
	uint			GetObjectCnt( void ) const;

public:
	vector<Quadrant*>	mQuadrants;

protected:
	Array<3>			mDim;			// each coord must be odd
	Array<3>			mDimHalf;		// pre-computed by constructor
	Array<3>			mCenterTuple;	// pre-computed by constructor

friend ostream& operator<<( ostream& strm, const Locus& o );
};

////////////////////////////////////////////////////////////////////////////////
/// @brief QuadrantObject is used to test visibility of entire quadrant.
class QuadrantObject : public FixedMesh
{
PREVENT_COPYING(QuadrantObject)
private:
	typedef FixedMesh Parent;
	enum { eQobj_POLYGON_CNT = 4,	// rectangles
           eQobj_VERTEX_CNT  = eQobj_POLYGON_CNT * 4 };
public:
	explicit			QuadrantObject( QuadrantPtrc q );
						~QuadrantObject();
	void				FinalConstruction( void ) { }	// constructor sets position/radius
	int					Visible( void );
	bool				Reproducible( void ) { return false; }
	uint				GetVertexCnt( void )   { return eQobj_VERTEX_CNT; }
	uint				GetPolygonCnt( void )  { return eQobj_POLYGON_CNT; }
	const WorldVertex*	GetWorldVertexs( void ) { return mWorldVertexs; }
	const string		GetName( void ) { return string("QuadrantObject"); }

	void Draw( void ) { }							// OBJECT NOP
	void SetVertexCnt( uint vcnt ) { }				// OBJECT NOP
	void SetWorldVertexs( WorldVertex* wv ) { }		// OBJECT NOP

private:
	WorldVertex			mWorldVertexs[eQobj_VERTEX_CNT];
};

#endif // ENG_WORLD_HH
