/*****************************************************************************
 * $Id: mod_pony_world.cc,v 1.1 2006/11/24 21:56:58 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Module World.
 *//*
 * LEGAL:	COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#define MOD_PONY_WORLD_CC 1
#include "mod_pony_common.hh"
#include "mod_pony_world.hh"
#include "mod_pony_init.hh"
#include "mod_pony_buildings.hh"
#include "mod_pony_sky.hh"
#include "mod_pony_terrain.hh"
#include "mod_pony_water.hh"
#include "eng_world.hh"
#include "gfx_random.hh"

extern SafePtr<Runway> gRunway;

/*****************************************************************************
 * Make runway at center of world.
 *****************************************************************************/
static void
MakeRunway( void )
{
	// Make Runway only once.  It persists as Runway::Reproducible() is false.
	if ( gRunway == NULL )
	{
		ASSERT_RUN_ONCE;

	    Position posRunway = gWorld.GetCenter();
	    posRunway.z -= Runway::DefaultLength();
		gRunway = new Runway( gWorld.GetCenterQuadrant(), posRunway );
	}
}

/*****************************************************************************
 * See World::Make1().
 *****************************************************************************/
void
ModulePonyWorld::Make( void )
{
	// -------------------------------------------------------------------------
	// ------- members not yet initialized (gWorld unavailable) ----------------
	// -------------------------------------------------------------------------

	// Preparations prior to World::Make().
	if ( ! gEng.IfQuiet() )
	{
		Splash();
		*gStreamOut << "Making the simulated world..." << endl;
	}

    // Set dimensions of World and Locus.
	Array<3> dimLocus;
	switch ( gWorldParm.mWorldSize )
	{
		case eWorldSize_NORMAL: default:
    		gWorldParm.mWorldDim[XX] = 24;  // this proves the world is flat
    		gWorldParm.mWorldDim[YY] = 2;
            gWorldParm.mWorldDim[ZZ] = 24;
    		dimLocus[XX] = 7;
    		dimLocus[YY] = gWorldParm.mWorldDim[YY];
    		dimLocus[ZZ] = 7;
		break;

		case eWorldSize_LARGE:
    		gWorldParm.mWorldDim[XX] = 32;
            gWorldParm.mWorldDim[YY] = 2;
            gWorldParm.mWorldDim[ZZ] = 32;
    		dimLocus[XX] = 11;
    		dimLocus[YY] = gWorldParm.mWorldDim[YY];
    		dimLocus[ZZ] = 11;
		break;

		case eWorldSize_HUGE:
    		gWorldParm.mWorldDim[XX] = 48;
    		gWorldParm.mWorldDim[YY] = 2;
            gWorldParm.mWorldDim[ZZ] = 48;
    		dimLocus[XX] = 15;
    		dimLocus[YY] = gWorldParm.mWorldDim[YY];
    		dimLocus[ZZ] = 15;
		break;
	}

    ASSERT( gWorldParm.mWorldDim[XX] == gWorldParm.mWorldDim[ZZ] );
    ASSERT( gWorldParm.mWorldDim[YY] == dimLocus[YY] );

    ASSERT( dimLocus[XX] <= gWorldParm.mWorldDim[XX] );  // Locus shouldn't be greater than World
    ASSERT( dimLocus[YY] <= gWorldParm.mWorldDim[YY] );
    ASSERT( dimLocus[ZZ] <= gWorldParm.mWorldDim[ZZ] );

	// -------------------------------------------------------------------------
	// -------------------- Make the World in phases ---------------------------
	// -------------------------------------------------------------------------

	// First phase:
	//
	// Call World::Make1() which sets the dimensions of the world.
	Parent::Make1( true, // flightSim=true
                   gWorldParm.mWorldDim,
                   gWorldParm.mQuadrantEdge,
				   gWorldParm.mMargin,
                   dimLocus );

	// Intermediate phase:

	// For Object::Visible().
ASSERT( mQuadrantEdge > 0.0 );
	SetObjectSmallDist( WORLD(15000.0) );
	SetObjectLargeDist( WORLD( mQuadrantEdge * ((mDimLocus[XX]>>1)+1) ) );
	gEng.SetLodUser( gEng.GetLodUser() );	// changes mObjectSmallDist2

	// Final phase:
	//
	// Call World::Make2() which constructs and populates the Quadrants.
	// Quadrant::Quadrant() calls the virtual World::PopulateQuadrant()
	// to populate the quadrant specifically for a module.
	Parent::Make2();
	mMade = true;

	// -------------------------------------------------------------------------
	// ------------------ World is made (gWorld is available) ------------------
	// -------------------------------------------------------------------------

    // Make runway at center of world.
    MakeRunway();

	// Splash with a count of objects in the locus.
	// No total object count is available as Quadrants outside the Locus are empty.
	mLocus.Update( mCenter );
	if ( ! gEng.IfQuiet() )
	{
		*gEng.mStreamStatus
			<< "Made [" << mDimQuadrant[XX] << "]["
	                    << mDimQuadrant[YY] << "]["
	                    << mDimQuadrant[ZZ] << "] "
	        << "quadrants " << "(" << mObjectCnt << " objects in locus). \n";
	}

	// Move Eye to center.
	Position posEye = mCenter;
	posEye.y += WORLD(20.0);
    posEye.z -= gRunway->Length() * 0.5 - gEng.GetPerspectiveCompensation();
	gEng.mEye.Init( GetCenterQuadrant(), posEye );
}

/*****************************************************************************
 * Called by Quadrant::Populate() as it defers to an outside module.
 *****************************************************************************/
void
ModulePonyWorld::PopulateQuadrant( QuadrantPtrConst q )
{
	// Reload the same random seed to generate the same sequence of psuedo random numbers
	// in order to reproduce randomly-constructed objects the same way.
	randomize( gEng.GetSeed() );

	// - - - - - - - - - - - - - - - - -
	// Non-reproducible objects
	// - - - - - - - - - - - - - - - - -
	if ( q->GetState() == eQuadrantState_EMPTY )
	{
		// Quadrant enters the shrunk state when it has only non-reproducible objects
		q->SetState( eQuadrantState_SHRUNK );

        // (historical: where construction of non-reproducible objects used to be)
	}

	// - - - - - - - - - - - - - - - - -
	// Reproducible objects
	// - - - - - - - - - - - - - - - - -
	if ( q->GetState() != eQuadrantState_POPULATED )
	{
		q->SetState( eQuadrantState_POPULATED );

   		new Sky( q );
   		new Terrain( q, SafePtr<Water>( new Water(q) ) );

   		// Add nothing more to edge quadrants.
   		// Edge quadrants should be (mostly) empty to avoid objects magically/annoyingly
   		// appearing when the viewpoint wraps around the world.
   		if ( ! IfEdgeQuadrant( q ) )
   		{
   			// Center quadrant is a special case.
   			if ( IfCenterQuadrant( q ) ) MakeRunway();
   		}
	}
}

/*****************************************************************************
 * Returns true is an object's position/radius lies too close to the initial
 * viewpoint.  Eg, don't want the program to startup inside of a mountain.
 *****************************************************************************/
bool
ModulePonyWorld::TooCloseToInitialViewpoint( const Posrad& posrad ) const
{
	return gRunway->IfOverlaps( posrad );  // not IfInside()
}

/*****************************************************************************
 * Called when LOD was changed.
 *****************************************************************************/
void
ModulePonyWorld::LodUserChange( void )
{
	// mObjectSmallDist2 is used by Object::Visible().
	// Only the World class really knows how far away a small object becomes invisible.
	// Eg, trees will be shown even if a few pixels wide if max LOD.
	switch ( gEng.GetLodUser() )
	{
		case eLodUser_MAX:
		SetObjectSmallDist( WORLD(55000.0) );
		break;

		case eLodUser_HIGH:
		SetObjectSmallDist( WORLD(30000.0) );
		break;

		default:
		SetObjectSmallDist( WORLD(15000.0) );
		break;

		case eLodUser_MIN: case eLodUser_LOW:
		SetObjectSmallDist( WORLD(7500.0) );
		break;
	}
}

/*****************************************************************************
 * @return Y at ground-level.
 *****************************************************************************/
fp
ModulePonyWorld::GroundLevel( void ) const
{
    return GROUND_LEVEL;
}
