/*****************************************************************************
 * $LastChangedDate: 2010-10-23 22:48:47 -0400 (Sat, 23 Oct 2010) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Planet class is a Facade over flat or spherical world.
 *//*
 * LEGAL:   COPYRIGHT (C) 2008 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#define WORLD_PLANET_FLAT_CC 1
#define NEED_PLANET_CLASS 1
#include "base/module.hh"
#include "base/conf.hh"
using namespace base;
#include "math/module.hh"
#include "math/funcs_vector.hh"
using namespace math;
#include "glue/module.hh"
using namespace glue;
#include "graph/module.hh"
#include "graph/model_cache.hh"
using namespace graph;
#include "object/module.hh"
using namespace object;
#include "world/module.hh"
#include "world/defs.hh"
#include "world/conf.hh"
#include "world/planet.hh"
#include "world/planet_flat.hh"

namespace world {

////////////////////////////////////////////////////////////////////////////////
//////////////////////////////  FlatPlanet  ////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * ctor/dtor.
 *****************************************************************************/
FlatPlanet::FlatPlanet( void )
:   mRootObject(NULL),
    mSphereNW(Degree(0),Degree(0),Meter(0)),
    mSphereSE(Degree(0),Degree(0),Meter(0)),
    mFlatNW(0,0,0),
    mFlatSE(0,0,0),
    mSimLatRatio(0),
    mSimLonRatio(0),
    mTangentMatrix()  // remains an identity matrix (no precomputing)
{
    // Load terrain model into scene-graph.
    // NOTE: Call the direct LoadModel().
    // The normal LoadModel() is based on GeoVertex2WorldVertex() which cause a paradox.
    shptr<Graph> flatModel = GET_MODEL_CACHE().LoadModel( WORLD_CONF.mFlatModelFile );
    flatModel->SetName( "terrain" );
    mRootObject = new Object( flatModel, WorldVertex(0,0,0) );
    WORLD_CONF.mFlatModelRotationList.Apply( *mRootObject );
    mRootObject->Scale( WORLD_CONF.mFlatModelScale );
    mRootObject->Translate( WORLD_CONF.mFlatModelOffset );

    // Planet is a collidable object.
    mRootObject->SetCollidable( true );

    // Precompute for conversion between graphical and spherical coordinates.
    mSphereNW = WORLD_CONF.mFlatModelNW;  // misnomers
    mSphereSE = WORLD_CONF.mFlatModelSE;
    const fp          radius = mRootObject->GetRadius(); ASSERT( radius > 0 );
    const WorldVertex center = mRootObject->GetPosition();
    mFlatNW[LAT] = center[LAT] - radius;
    mFlatNW[LON] = center[LON] - radius;
    mFlatNW[ALT] = 0.0f;
    mFlatSE[LAT] = center[LAT] + radius;
    mFlatSE[LON] = center[LON] + radius;
    mFlatSE[ALT] = 0.0f;

    // Precompute ratios of sim:lat and sim:lon.
    // Note that the span of longitude becomes zero at poles,
    // therefore, there are separate ratios.
    mSimLatRatio = (mFlatNW[LAT] - mFlatSE[LAT]) / (mSphereNW.mLat - mSphereSE.mLat);
    mSimLonRatio = (mFlatNW[LON] - mFlatSE[LON]) / (mSphereNW.mLon - mSphereSE.mLon);

#if DEBUG
    // Test roundtrip coordinate conversion (input == output == input).
    const SphereVertex debugSV( WORLD_CONF.mFlatModelNW );
    const WorldVertex  debugWV = SphereVertex2WorldVertex( debugSV );
    const SphereVertex debugSV2 = WorldVertex2SphereVertex( debugWV );
    ASSERT( FP_EQ( debugSV.mLat.FP(), debugSV2.mLat.FP() ) );
    ASSERT( FP_EQ( debugSV.mLon.FP(), debugSV2.mLon.FP() ) );
    ASSERT( FP_EQ( debugSV.mAlt.FP(), debugSV2.mAlt.FP() ) );

    // Assigned?
    ASSERT( mRootObject != NULL );
    ASSERT( mSphereNW.mLat != Degree(0.0f) or mSphereSE.mLat != Degree(0.0f) );
    ASSERT( Distance( mFlatNW, mFlatSE ) > 0.0f );
    ASSERT( ABS(mSimLatRatio) > 0.0f );
    ASSERT( ABS(mSimLonRatio) > 0.0f );
#endif // DEBUG
}

FlatPlanet::~FlatPlanet()
{
    // NOP
}

/*****************************************************************************
 * @return The planet's node.
 *****************************************************************************/
shptr<Object>
FlatPlanet::GetRootObject( void )
{
ASSERT( mRootObject != NULL );  // ctor makes it
    return mRootObject.PTR();
}

/*****************************************************************************
 * Compute a matrix that is tangent to a point on the world.
 *****************************************************************************/
const Matrix&
FlatPlanet::GetTangentMatrix( UNUSED const SphereVertex& sv )
{
    return mTangentMatrix;
}

/*****************************************************************************
 * Compute a normalized "up vector" extending from point on the world.
 *****************************************************************************/
WorldVertex
FlatPlanet::ComputeUpVector( UNUSED const SphereVertex& sv )
{
    return WorldVertex( 0.0f, 0.0f, 1.0f );
}

/*****************************************************************************
 * Convert spherical coordinates to a 3D vertex.
 *****************************************************************************/
WorldVertex
FlatPlanet::SphereVertex2WorldVertex( const SphereVertex& sv )
{
ASSERT( ABS(mSimLatRatio) > 0 and ABS(mSimLonRatio) > 0 );  // may be negative
ASSERT( MeterSimRatio() > 0 );

    // Adjust altitude because of particular osgdem conversion.
    const fp altOffset = WORLD_CONF.mFlatModelMeterOffset[ALT].FP() * MeterSimRatio();

    // lat,lon in degree: deltaDeg * (sim/deg) = sim
    // alt in meters    : deltaAlt / (meter/sim) = deltaAlt * sim/meter = sim
    fp x = (sv.mLat      - mSphereSE.mLat     ) * mSimLatRatio + mFlatSE[LAT];  // Degree
    fp y = (sv.mLon      - mSphereSE.mLon     ) * mSimLonRatio + mFlatSE[LON];  // Degree
    fp z = (sv.mAlt.FP() - mSphereSE.mAlt.FP() + altOffset) / MeterSimRatio();  // Meter

    return WorldVertex( x, y, z );
}

/*****************************************************************************
 * Convert 3D vertex to spherical coordinates.
 *****************************************************************************/
SphereVertex
FlatPlanet::WorldVertex2SphereVertex( const WorldVertex& wv )
{
ASSERT( ABS(mSimLatRatio) > 0 and ABS(mSimLonRatio) > 0 );  // may be negative
ASSERT( MeterSimRatio() > 0 );
ASSERT( WORLD_CONF.mWorldAltitudeMin < WORLD_CONF.mWorldAltitudeMax );

    Degree lat = Degree((wv[LAT] - mFlatSE[LAT]) / mSimLatRatio + mSphereSE.mLat);
    Degree lon = Degree((wv[LON] - mFlatSE[LON]) / mSimLonRatio + mSphereSE.mLon);
    Meter  alt = Meter( (wv[ALT] - mFlatSE[ALT]) * MeterSimRatio());

    // Adjust altitude because of particular osgdem conversion.
    alt -= Meter(WORLD_CONF.mFlatModelMeterOffset[ALT]);
ASSERT( FP_LE<fp>( ABS(lat), 360.0f ) );
ASSERT( FP_LE<fp>( ABS(lon), 360.0f ) );
ASSERT( alt >= WORLD_CONF.mWorldAltitudeMin );
ASSERT( alt <= WORLD_CONF.mWorldAltitudeMax );
    return SphereVertex( lat, lon, alt );

}

/*****************************************************************************
 * @return meter / graphicCoord
 *****************************************************************************/
fpx
FlatPlanet::MeterSimRatio( void )
{
ASSERT( WORLD_CONF.mFlatModelMeterSimRatio > 0 );

    return WORLD_CONF.mFlatModelMeterSimRatio;
}

/*****************************************************************************
 * Compute the radius of the planet in graphical coordinates.
 *****************************************************************************/
fp
FlatPlanet::RadiusGraphical( void )
{
    const fp radius = mRootObject->GetRadius();
    ASSERT( radius > 0 );
    return radius;
}

/*****************************************************************************
 * @return Volume of world in 3D space.
 *****************************************************************************/
BoxVolume
FlatPlanet::GetVolume( void )
{
    SphereVertex sv0 = WORLD_CONF.mFlatModelNW;
    SphereVertex sv1 = WORLD_CONF.mFlatModelSE;
    sv0.mAlt = WORLD_CONF.mFlatModelMeterAltMin;  // corner at lowest altitude
    sv1.mAlt = WORLD_CONF.mFlatModelMeterAltMax;  // corner at highest altitude
    return BoxVolume( SphereVertex2WorldVertex( sv0 ),
                      SphereVertex2WorldVertex( sv1 ) );
}

} // namespace world
