/*****************************************************************************
 * $Id: eng_dyna_model.cc,v 1.4 2006/08/20 16:34:17 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	DynaModel class.
 *//*
 * LEGAL:	COPYRIGHT (C) 2006 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#define ENG_DYNA_MODEL_CC 1
#include "eng.hh"
#include "eng_dyna.hh"
#include "eng_dyna_model.hh"
#include "eng_import.hh"

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////  DynaModel  ///////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Construct DynaModel from a set of imported 3D models.
 *****************************************************************************/
DynaModel::DynaModel( QuadrantPtrc q, const Position& pos, const vector<string>& fnameModelVec )
:   Parent(q),
    mNeedRecalcWorldVertexs(true),
    mNeedRecalcNormals(true),
    mWorldVertexs(NULL),
    mNormalsWorld(NULL),
    mModelsIdx(0),
	mVertexCntMax(0),
	mNormalCntMax(0)
{
	const uint modelsCnt = fnameModelVec.size(); ASSERT( modelsCnt > 0 );

	SetPosition( pos );
	GetQuadrant()->DynaMoved( *this, pos );		// changing position is translation

	// Import each 3D model given in the vector of model filenames.
	// This will set a vector of arrays of local vertexs/normals.
    mModels.resize( modelsCnt );  // allocates
	for ( uint modelsIdx = 0; modelsIdx < modelsCnt; ++modelsIdx )
	{
		// SUBTLE: Import class has SharedPtr members.
		// SharedPtr reference count will be incremented twice
		// when Import() copies to "import" and it is is copied to m*Shared,
		// but decremented once (to correct value) as "import" is an autovar.

    	if ( ! gImportFactory.Import( mModels[modelsIdx].mImportation, fnameModelVec[modelsIdx] ) )
		{
			// Luke: "I failed, Master Yoda.  You want the impossible!"
			// Yoda: "No good, at coding, you are.  Darth Gates, your master, he now is."
			*gStreamError << gImportFactory.GetErrorText() << endl;
			exit( 1 );
		}

		// If video card doesn't support full-screen antialiasing (FSAA),
		// then force using the non-vertex-array PolygonList::Draw() method
		// which does sw antialiasing (otherwise crafts will look bad).
		if ( ! gEng.IfFSAA() )
		{
            SharedPtr<PolygonList> polygonList = mModels[modelsIdx].mImportation.GetPolygonList();
            polygonList->mDrawParm.EnableAntialias( true );
            polygonList->mDrawParm.EnableVertexArray( false );
		}

    	// Track greatest counts of vertexs/normals in the polygon lists
    	// for later reallocation (first polygon list might not be the greatest).
		mVertexCntMax = MAX( mVertexCntMax, mModels[modelsIdx].mImportation.GetVertexCnt() );
		mNormalCntMax = MAX( mNormalCntMax, mModels[modelsIdx].mImportation.GetNormalCnt() );

        // Make per-instance copies of local vertexs/normals imported from model.
        // Transforming parts of a mesh affects per-instance vertexs/normals while
        // the original unmodified vertexs/normals are saved in the Importations object.
        mModels[modelsIdx].SetLocalVertexs( GET_PTR(mModels[modelsIdx].mImportation.GetLocalVertexs()),
                                            mModels[modelsIdx].mImportation.GetVertexCnt() );
        mModels[modelsIdx].SetLocalNormals( GET_PTR(mModels[modelsIdx].mImportation.GetLocalNormals()),
                                            mModels[modelsIdx].mImportation.GetNormalCnt() );
	}

	// Activate the first model.
	SetModel( 0 );

	// Now that a polygon list is active, check Get*() methods.
	ASSERT( mModels.size() == modelsCnt );
	ASSERT( GetVertexCnt()    > 0 );
	ASSERT( GetVertexCnt()    < MAX_VERTEXS );
	ASSERT( GetVertexCntMax() > 0 );
	ASSERT( GetVertexCntMax() < MAX_VERTEXS );
	ASSERT( GetNormalCnt()    > 0 );
	ASSERT( GetNormalCnt()    < MAX_POLYGONS );
	ASSERT( GetNormalCntMax() > 0 );
	ASSERT( GetNormalCntMax() < MAX_POLYGONS );
	ASSERT( GetLocalVertexs() != NULL );
	ASSERT( GetLocalNormals() != NULL );

	// Importing allocated local vertexs/normals.
	// Now allocate enough world vertexs/normals to accomodate any polygon list.
	mWorldVertexs = new WorldVertex[ GetVertexCntMax() + 2];
	mNormalsWorld = new Normal[      GetNormalCntMax() + 2];
	SetNeedRecalc();  // should be called whenever arrays are modified

	FinalConstruction();
	DEBUG_CODE( CheckObjectCtor(); )
}
void
DynaModel::FinalConstruction( void )
{
	CalcRadiusAdjustPosition();

	// Create Detector object (after calculating radius).
	// ~Object() will delete it
	new DetectorRadius( *this );  // indeed, pointer isn't stored
}
DynaModel::~DynaModel()
{
	DELETE_ARR_NULL( mWorldVertexs );
	DELETE_ARR_NULL( mNormalsWorld );
	// (Let ~Object() delete Detector object)
	DEBUG_CODE( CheckObjectDtor(); )
}

/*****************************************************************************
 * Transform local vertexs to world vertexs.
 *****************************************************************************/
const WorldVertex*
DynaModel::GetWorldVertexs( void )
{
RETURN_IF_SAVED_DYNA_WORLD_VERTEXS // unless not saved this frame
	if ( mNeedRecalcWorldVertexs )
	{
		mNeedRecalcWorldVertexs = false;
		// local --> world.  Don't use mVertexCnt (per polygon list).
		TRANSFORM_VERTEX( mWorldVertexs, GetLocalVertexs(), GetVertexCnt(), mCm );
	}
SAVE_DYNA_WORLD_VERTEXS( mWorldVertexs )
	return mWorldVertexs;
}

/*****************************************************************************
 * Object::CalcNormals() doesn't apply as the imported 3D data specifies the normals.
 * Object::CalcNormals() depends on vertex order (counter-clockwise) but Blender
 * is free to put vertexs in any order as Blender computes its normals.
 * The normals are imported as "local normals" (invariant) as transformed into
 * "world normals" as the DynaModel object is transformed.
 *
 * "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.
 *****************************************************************************/
Normal*
DynaModel::GetWorldNormals( void )
{
	if ( mNeedRecalcNormals )
	{
		mNeedRecalcNormals = false;
		const uint normalCnt = GetNormalCnt();
		const Normal*      nl = GetLocalNormals();
		const LocalVertex* vl = GetLocalVertexs();
		const WorldVertex* wv = GetWorldVertexs();
		for ( uint i = 0; i < normalCnt; ++i )
		{
			// Index of world vertex where normal originates.
			const uint vix = nl[i].mVixNormOrig;

			// Turn normal into a local vertex.
			LocalVertex norml, normw;
			norml.x = nl[i].mNorm.x + vl[vix].x;
			norml.y = nl[i].mNorm.y + vl[vix].y;
			norml.z = nl[i].mNorm.z + vl[vix].z;

			// Transform local vertex (the normal) into world space.
			RotTrans( normw.x, normw.y, normw.z,
                      norml.x, norml.y, norml.z, mCm );

			// Transform world vertex (the normal) back into normal space.
			// The normal vector's point in world space has the same angles of rotation
			// as the world vertexs.
			mNormalsWorld[i].mNorm.x      = normw.x - wv[vix].x;
			mNormalsWorld[i].mNorm.y      = normw.y - wv[vix].y;
			mNormalsWorld[i].mNorm.z      = normw.z - wv[vix].z;
			mNormalsWorld[i].mVixNormOrig = vix;	// in case normals array was reallocated
		}
	}
	return mNormalsWorld;
}

/*****************************************************************************
 * Translate DynaModel.
 * Does NOT compensate for dropped frames.
 *****************************************************************************/
void
DynaModel::Translate( uint axis, fp inc )
{
	TranslateMatrixLocal( axis, (fpx)inc, mXm );
	SyncMatrix();
	Fence();
	GetQuadrant()->DynaMoved( *this );
	SetNeedRecalc();
}

/*****************************************************************************
 * Rotate DynaModel.
 * Does NOT compensate for dropped frames.
 *****************************************************************************/
void
DynaModel::Rotate( uint axis, fp deg )
{
	if ( UX(IFNAN(deg)) ) // prevent NaN disrupting matrix
		return;
	deg *= mResponse;
	RotateMatrixLocal( axis, (fpx)deg, mXm );
	SyncMatrix();
	SetNeedRecalc();
}

/*****************************************************************************
 * Draw model.
 *****************************************************************************/
void
DynaModel::Draw( void )
{
	GetPolygonList().Draw( this );
}

/*****************************************************************************
 * Switch to a model by index.
 *****************************************************************************/
void
DynaModel::SetModel( uint modelsIdx )
{
	ASSERT( modelsIdx < mModels.size() );
	mModelsIdx = modelsIdx;

	// Switching the polygon list index switches local vertex/normals.
	// But world vertexs/normals must be recalculated anew.
	SetNeedRecalc();
}

/*****************************************************************************
 * DEPRECATED: Instead, to speed startup, pre-rotate the model in Blender using
 * the "Rotate:" text-entry widget of the Blender-to-Palomino export script.
 *
 * Rotate the model.  This rotates the local vertexs (not its matrix).
 * Useful when an imported 3D model has a different rotation than it should
 * have in Palomino.
 *****************************************************************************/
#if 0
// Keep this (was difficult to write, may be needed again).
void
DynaModel::RotateModel( uint axis, fp deg )
{
	if ( UX(IFNAN(deg)) ) return; // prevent NaN disrupting matrix

	// The usual transformation thru a matrix.
	Matrix m;
	IdentityMatrix( m );
	RotateMatrixLocal( axis, deg, m );

	const uint vertexCnt = GetVertexCnt();
	for ( uint i = 0; i < vertexCnt; ++i )
	{
		fp x = mLocalVertexs[i].x;
		fp y = mLocalVertexs[i].y;
		fp z = mLocalVertexs[i].z;
		mLocalVertexs[i].x = x * m[Xx] + y * m[Xy] + z * m[Xz];
		mLocalVertexs[i].y = x * m[Yx] + y * m[Yy] + z * m[Yz];
		mLocalVertexs[i].z = x * m[Zx] + y * m[Zy] + z * m[Zz];
	}

	const uint normalCnt = GetNormalCnt();
	for ( uint i = 0; i < normalCnt; ++i )
	{
		fp x = mLocalNormals[i].mNorm.x;
		fp y = mLocalNormals[i].mNorm.y;
		fp z = mLocalNormals[i].mNorm.z;
		mLocalNormals[i].mNorm.x = x * m[Xx] + y * m[Xy] + z * m[Xz];
		mLocalNormals[i].mNorm.y = x * m[Yx] + y * m[Yy] + z * m[Yz];
		mLocalNormals[i].mNorm.z = x * m[Zx] + y * m[Zy] + z * m[Zz];
	}
}
#endif // 0
