//
// EnginePlugin
//
// This example plugin demostrates:
// - loading proprietary file format,
// - how to use the file duration methods
// - how to set the value of a parameter when another parameter changes
// - how to save and load file handles
// - how to use TimeContextC
//
// See MeshC.cpp for how to save a file handle.
// See ASELoaderC.cpp for how to load images using the request_import() method of ImportInterfaceC.
// 

#define WIN32_LEAN_AND_MEAN     // Exclude rarely-used stuff from

// Windows headers
#include <windows.h>
#include <gl\gl.h>
#include <gl\glu.h>

// Demopaja headers
#include "DemopajaVersion.h"
#include "PajaTypes.h"
#include "ClassDescC.h"
#include "ImportableImageI.h"
#include "OpenGLDeviceC.h"
#include "OpenGLViewportC.h"
#include "EnginePlugin.h"
#include "FileIO.h"
#include "AutoGizmoC.h"
#include "ASELoaderC.h"

using namespace PajaTypes;
using namespace PluginClass;
using namespace PajaSystem;
using namespace Edit;
using namespace Composition;
using namespace Import;
using namespace FileIO;

using namespace EnginePlugin;


//////////////////////////////////////////////////////////////////////////
//
//  ASE importer class descriptor.
//

ASEImportDescC::ASEImportDescC()
{
	// empty
}

ASEImportDescC::~ASEImportDescC()
{
	// empty
}

void*
ASEImportDescC::create()
{
	return ASEImportC::create_new();
}

int32
ASEImportDescC::get_classtype() const
{
	return CLASS_TYPE_FILEIMPORT;
}

SuperClassIdC
ASEImportDescC::get_super_class_id() const
{
	return SUPERCLASS_IMPORT;
}

ClassIdC
ASEImportDescC::get_class_id() const
{
	return CLASS_ASE_IMPORT;
}

const char*
ASEImportDescC::get_name() const
{
	return "ASE 3D Mesh";
}

const char*
ASEImportDescC::get_desc() const
{
	return "Importer for ASE 3D meshes";
}

const char*
ASEImportDescC::get_author_name() const
{
	return "Mikko \"memon\" Mononen";
}

const char*
ASEImportDescC::get_copyright_message() const
{
	return "Copyright (c) 2000 Moppi Productions";
}

const char*
ASEImportDescC::get_url() const
{
	return "http://moppi.inside.org/demopaja/";
}

const char*
ASEImportDescC::get_help_filename() const
{
	return "res://ASEhelp.html";
}

uint32
ASEImportDescC::get_required_device_driver_count() const
{
	return 1;
}

const ClassIdC&
ASEImportDescC::get_required_device_driver( uint32 ui32Idx )
{
	return PajaSystem::CLASS_OPENGL_DEVICEDRIVER;
}

uint32
ASEImportDescC::get_ext_count() const
{
	return 1;
}

const char*
ASEImportDescC::get_ext( uint32 ui32Index ) const
{
	if( ui32Index == 0 )
		return "ASE";
	return 0;
}



//////////////////////////////////////////////////////////////////////////
//
//  ASE player effect class descriptor.
//


EngineDescC::EngineDescC()
{
	// empty
}

EngineDescC::~EngineDescC()
{
	// empty
}

void*
EngineDescC::create()
{
	return (void*)EngineEffectC::create_new();
}

int32
EngineDescC::get_classtype() const
{
	return CLASS_TYPE_EFFECT;
}

SuperClassIdC
EngineDescC::get_super_class_id() const
{
	return SUPERCLASS_EFFECT;
}

ClassIdC
EngineDescC::get_class_id() const
{
	return CLASS_ASEPLAYER_EFFECT;
};

const char*
EngineDescC::get_name() const
{
	return "ASE Player";
}

const char*
EngineDescC::get_desc() const
{
	return "ASE Player";
}

const char*
EngineDescC::get_author_name() const
{
	return "Mikko \"memon\" Mononen";
}

const char*
EngineDescC::get_copyright_message() const
{
	return "Copyright (c) 2000 Moppi Productions";
}

const char*
EngineDescC::get_url() const
{
	return "http://moppi.inside.org/demopaja/";
}

const char*
EngineDescC::get_help_filename() const
{
	return "res://enginehelp.html";
}

uint32
EngineDescC::get_required_device_driver_count() const
{
	return 1;
}

const ClassIdC&
EngineDescC::get_required_device_driver( uint32 ui32Idx )
{
	return PajaSystem::CLASS_OPENGL_DEVICEDRIVER;
}

uint32
EngineDescC::get_ext_count() const
{
	return 0;
}

const char*
EngineDescC::get_ext( uint32 ui32Index ) const
{
	return 0;
}

//////////////////////////////////////////////////////////////////////////
//
// Global descriptors, which will be returned to the Demopaja.
//

EngineDescC		g_rEngineDesc;
ASEImportDescC	g_rASEImportDesc;


#ifndef PAJAPLAYER

//////////////////////////////////////////////////////////////////////////
//
// The DLL
//

BOOL APIENTRY
DllMain( HANDLE hModule, DWORD ulReasonForCall, LPVOID lpReserved )
{
	return TRUE;
}

// Returns number of classes inside this plugin DLL.
__declspec( dllexport )
int32
get_classdesc_count()
{
	return 2;
}

// Returns class descriptors of the plugin classes.
__declspec( dllexport )
ClassDescC*
get_classdesc( int32 i )
{
	if( i == 0 )
		return &g_rEngineDesc;
	else if( i == 1 )
		return &g_rASEImportDesc;
	return 0;
}

// Returns the API version this DLL was made with.
__declspec( dllexport )
int32
get_api_version()
{
	return DEMOPAJA_VERSION;
}

// Returns the DLL name.
__declspec( dllexport )
char*
get_dll_name()
{
	return "engineplugin.dll - 3D Animation example (c) 2000 memon/moppi productions";
}


#endif	// PAJAPLAYER


//////////////////////////////////////////////////////////////////////////
//
// ASE Importer class implementation.
//

ASEImportC::ASEImportC() :
	m_bFileRefsValid( false ),
	m_i32CurrentFrame( 0 )
{
	// empty
}

ASEImportC::ASEImportC( EditableI* pOriginal ) :
	ImportableI( pOriginal ),
	m_bFileRefsValid( false ),
	m_i32CurrentFrame( 0 )
{
	// empty
}

ASEImportC::~ASEImportC()
{
	// Return if this is a clone.
	if( get_original() )
		return;

	uint32	i;
	for( i = 0; i < m_rScenegraph.size(); i++ )
		delete m_rScenegraph[i];
	for( i = 0; i < m_rCameras.size(); i++ )
		delete m_rCameras[i];
	for( i = 0; i < m_rLights.size(); i++ )
		delete m_rLights[i];
}

ASEImportC*
ASEImportC::create_new()
{
	return new ASEImportC;
}

DataBlockI*
ASEImportC::create()
{
	return new ASEImportC;
}

DataBlockI*
ASEImportC::create( EditableI* pOriginal )
{
	return new ASEImportC( pOriginal );
}

void
ASEImportC::copy( EditableI* pEditable )
{
	// Importables which loads the data from a file does
	// not have to implement this method, since they will
	// not be duplicated.
}

void
ASEImportC::restore( EditableI* pEditable )
{
	ASEImportC*	pFile = (ASEImportC*)pEditable;

	m_sFileName = pFile->m_sFileName;
	m_bFileRefsValid = pFile->m_bFileRefsValid;
	m_rFileRefs = pFile->m_rFileRefs;

	m_rScenegraph = pFile->m_rScenegraph;
	m_rCameras = pFile->m_rCameras;
	m_rLights = pFile->m_rLights;
	m_i32FPS = pFile->m_i32FPS;
	m_i32TicksPerFrame = pFile->m_i32TicksPerFrame;
	m_i32FirstFrame = pFile->m_i32FirstFrame;
	m_i32LastFrame = pFile->m_i32LastFrame;

}

const char*
ASEImportC::get_filename()
{
	return m_sFileName.c_str();
}

void
ASEImportC::set_filename( const char* szName )
{
	m_sFileName = szName;
}

bool
ASEImportC::load_file( const char* szName, PajaSystem::DemoInterfaceC* pInterface )
{
	ASELoaderC	rLdr;

	if( !rLdr.load( szName, pInterface ) )
		return false;

	uint32	i;
	for( i = 0; i < m_rScenegraph.size(); i++ )
		delete m_rScenegraph[i];
	for( i = 0; i < m_rCameras.size(); i++ )
		delete m_rCameras[i];
	for( i = 0; i < m_rLights.size(); i++ )
		delete m_rLights[i];

	rLdr.get_cameras( m_rCameras );
	rLdr.get_lights( m_rLights );
	rLdr.get_scenegraph( m_rScenegraph );
	rLdr.get_time_parameters( m_i32FPS, m_i32TicksPerFrame, m_i32FirstFrame, m_i32LastFrame );

	m_sFileName = szName;

	m_bFileRefsValid = false;

	return true;
}


void
ASEImportC::initialize( uint32 ui32Reason, PajaSystem::DemoInterfaceC* pInterface )
{
	ImportableI::initialize( ui32Reason, pInterface );
}

ClassIdC
ASEImportC::get_class_id()
{
	return CLASS_ASE_IMPORT;
}

SuperClassIdC
ASEImportC::get_super_class_id()
{
	return SUPERCLASS_IMPORT;
}

const char*
ASEImportC::get_class_name()
{
	return "ASE 3D Mesh";
}

const char*
ASEImportC::get_info()
{
	static char	szInfo[256];
	_snprintf( szInfo, 255, "%d meshes, %d cameras, %d lights", m_rScenegraph.size(), m_rCameras.size(), m_rLights.size() );
	return szInfo;
}

ClassIdC
ASEImportC::get_default_effect()
{
	return CLASS_ASEPLAYER_EFFECT;
}

void
ASEImportC::eval_state( int32 i32Time )
{
	if( !m_pDemoInterface )
		return;

	TimeContextC* pTimeContext = m_pDemoInterface->get_time_context();

	int32	i32Length = (m_i32LastFrame - m_i32FirstFrame) * m_i32TicksPerFrame;

	if( i32Length > 0 ) {
		m_i32CurrentFrame = pTimeContext->convert_time_to_fps( i32Time, m_i32FPS ) * (float64)m_i32TicksPerFrame;
		m_i32CurrentFrame %= i32Length;
		m_i32CurrentFrame += m_i32FirstFrame * m_i32TicksPerFrame;

		while( m_i32CurrentFrame < (m_i32FirstFrame * m_i32TicksPerFrame) )
			m_i32CurrentFrame += i32Length;
		while( m_i32CurrentFrame >= (m_i32LastFrame * m_i32TicksPerFrame) )
			m_i32CurrentFrame -= i32Length;
	}
	else
		m_i32CurrentFrame = 0;
}

//
// Returns the duration of the file. The original timecode is in frames per second (FPS).
// TimeContextC has a method to convert FPS timecode to Demopaja timecode.
// FirstFrame and LastFrame are in frames.
//
int32
ASEImportC::get_duration()
{
	if( !m_pDemoInterface )
		return -1;
	TimeContextC* pTimeContext = m_pDemoInterface->get_time_context();
	int32	i32Length = m_i32LastFrame - m_i32FirstFrame;
	return pTimeContext->convert_fps_to_time( i32Length, m_i32FPS );
}


//
// The two methods defines the range which is displayed in the GUI.
// Demopaja interpoaltes values between the range.
//
float32
ASEImportC::get_start_label()
{
	return m_i32FirstFrame;
}


float32
ASEImportC::get_end_label()
{
	return m_i32LastFrame;
}

void
ASEImportC::count_file_references( MeshC* pMesh )
{
	if( pMesh->get_texture() )
		m_rFileRefs.push_back( pMesh->get_texture() );

	for( uint32 i = 0; i < pMesh->get_child_count(); i++ )
		count_file_references( (MeshC*)pMesh->get_child( i ) );
}

//
// If you loaded files using the ImportInterfaceC you must implement
// these two methods. The first returns number of files used and the second
// returns the used handles.
//
uint32
ASEImportC::get_reference_file_count()
{
	if( !m_bFileRefsValid ) {
		m_rFileRefs.clear();
		for( uint32 i = 0; i < m_rScenegraph.size(); i++ )
			count_file_references( m_rScenegraph[i] );
		m_bFileRefsValid = true;
	}

	return m_rFileRefs.size();
}

FileHandleC*
ASEImportC::get_reference_file( PajaTypes::uint32 ui32Index )
{
	if( !m_bFileRefsValid ) {
		m_rFileRefs.clear();
		for( uint32 i = 0; i < m_rScenegraph.size(); i++ )
			count_file_references( m_rScenegraph[i] );
		m_bFileRefsValid = true;
	}

	if( ui32Index >= 0 && ui32Index < m_rFileRefs.size() )
		return m_rFileRefs[ui32Index];

	return 0;
}


uint32
ASEImportC::get_scenegraphitem_count()
{
	return m_rScenegraph.size();
}

MeshC*
ASEImportC::get_scenegraphitem( uint32 ui32Index )
{
	if( ui32Index >= 0 && ui32Index < m_rScenegraph.size() )
		return m_rScenegraph[ui32Index];
	return 0;
}

uint32
ASEImportC::get_camera_count()
{
	return m_rCameras.size();
}

CameraC*
ASEImportC::get_camera( uint32 ui32Index )
{
	if( ui32Index >= 0 && ui32Index < m_rCameras.size() )
		return m_rCameras[ui32Index];
	return 0;
}

uint32
ASEImportC::get_light_count()
{
	return m_rLights.size();
}

LightC*
ASEImportC::get_light( uint32 ui32Index )
{
	if( ui32Index >= 0 && ui32Index < m_rLights.size() )
		return m_rLights[ui32Index];
	return 0;
}

void
ASEImportC::get_time_parameters( int32& i32FPS, int32& i32TicksPerFrame, int32& i32FirstFrame, int32& i32LastFrame )
{
	i32FPS = m_i32FPS;
	i32TicksPerFrame = m_i32TicksPerFrame;
	i32FirstFrame = m_i32FirstFrame;
	i32LastFrame = m_i32LastFrame;
}

int32
ASEImportC::get_current_frame() const
{
	return m_i32CurrentFrame;
}


enum ASEImportChunksE {
	CHUNK_ASEIMPORT_BASE		= 0x1000,
	CHUNK_ASEIMPORT_MESH		= 0x2000,
	CHUNK_ASEIMPORT_CAMERA		= 0x3000,
	CHUNK_ASEIMPORT_LIGHT		= 0x4000,
};

const uint32	ASEIMPORT_VERSION = 1;

uint32
ASEImportC::save_scenegraphitem( SaveC* pSave, MeshC* pItem, uint32 ui32ParentId, uint32& ui32Id )
{
	uint32	ui32Error = IO_OK;
	uint32	ui32CurId = ui32Id;

	pSave->begin_chunk( CHUNK_ASEIMPORT_MESH, ASEIMPORT_VERSION );
		ui32Error = pSave->write( &ui32ParentId, sizeof( ui32ParentId ) );
		ui32Error = pSave->write( &ui32Id, sizeof( ui32Id ) );
		ui32Error = pItem->save( pSave );
		ui32Id++;
	pSave->end_chunk();

	for( uint32 i = 0; i < pItem->get_child_count(); i++ ) {
		ui32Error = save_scenegraphitem( pSave, (MeshC*)pItem->get_child( i ), ui32CurId, ui32Id );
	}

	return ui32Error;
}

uint32
ASEImportC::save( FileIO::SaveC* pSave )
{
	uint32		ui32Error = IO_OK;
	std::string	sStr;

	// file base
	pSave->begin_chunk( CHUNK_ASEIMPORT_BASE, ASEIMPORT_VERSION );
		sStr = m_sFileName;
		if( sStr.size() > 255 )
			sStr.resize( 255 );
		ui32Error = pSave->write_str( sStr.c_str() );
		ui32Error = pSave->write( &m_i32FPS, sizeof( m_i32FPS ) );
		ui32Error = pSave->write( &m_i32TicksPerFrame, sizeof( m_i32TicksPerFrame ) );
		ui32Error = pSave->write( &m_i32FirstFrame, sizeof( m_i32FirstFrame ) );
		ui32Error = pSave->write( &m_i32LastFrame, sizeof( m_i32LastFrame ) );
	pSave->end_chunk();

	// file data
	uint32	i;

	// cameras
	for( i = 0; i < m_rCameras.size(); i++ ) {
		pSave->begin_chunk( CHUNK_ASEIMPORT_CAMERA, ASEIMPORT_VERSION );
			ui32Error = m_rCameras[i]->save( pSave );
		pSave->end_chunk();
	}

	// lights
	for( i = 0; i < m_rLights.size(); i++ ) {
		pSave->begin_chunk( CHUNK_ASEIMPORT_LIGHT, ASEIMPORT_VERSION );
			ui32Error = m_rLights[i]->save( pSave );
		pSave->end_chunk();
	}

	uint32	ui32Id = 0;

	// scenegraph items
	for( i = 0; i < m_rScenegraph.size(); i++ ) {
		save_scenegraphitem( pSave, m_rScenegraph[i], 0xffffffff, ui32Id );
	}

	return ui32Error;
}

MeshC*
ASEImportC::find_parent( MeshC* pItem, uint32 ui32ParentId )
{
	MeshC*	pParent = 0;

	if( pItem->get_id() == ui32ParentId )
		return pItem;

	for( uint32 i = 0; i < pItem->get_child_count(); i++ ) {
		pParent = find_parent( (MeshC*)pItem->get_child( i ), ui32ParentId );
		if( pParent )
			return pParent;
	}

	return 0;
}

uint32
ASEImportC::load( FileIO::LoadC* pLoad )
{
	uint32	ui32Error = IO_OK;
	char	szStr[256];

	uint32	ui32Id = 0;
	uint32	ui32ParentId = 0;

	while( (ui32Error = pLoad->open_chunk()) == IO_OK ) {

		switch( pLoad->get_chunk_id() ) {
		case CHUNK_ASEIMPORT_BASE:
			if( pLoad->get_chunk_version() == ASEIMPORT_VERSION ) {
				ui32Error = pLoad->read_str( szStr );
				m_sFileName = szStr;
				ui32Error = pLoad->read( &m_i32FPS, sizeof( m_i32FPS ) );
				ui32Error = pLoad->read( &m_i32TicksPerFrame, sizeof( m_i32TicksPerFrame ) );
				ui32Error = pLoad->read( &m_i32FirstFrame, sizeof( m_i32FirstFrame ) );
				ui32Error = pLoad->read( &m_i32LastFrame, sizeof( m_i32LastFrame ) );
			}
			break;

		case CHUNK_ASEIMPORT_CAMERA:
			if( pLoad->get_chunk_version() == ASEIMPORT_VERSION ) {
				CameraC*	pCam = new CameraC;
				ui32Error = pCam->load( pLoad );
				m_rCameras.push_back( pCam );
			}
			break;

		case CHUNK_ASEIMPORT_LIGHT:
			if( pLoad->get_chunk_version() == ASEIMPORT_VERSION ) {
				LightC*	pCam = new LightC;
				ui32Error = pCam->load( pLoad );
				m_rLights.push_back( pCam );
			}
			break;

		case CHUNK_ASEIMPORT_MESH:
			if( pLoad->get_chunk_version() == ASEIMPORT_VERSION ) {
				MeshC*	pMesh = new MeshC;

				ui32Error = pLoad->read( &ui32ParentId, sizeof( ui32ParentId ) );
				ui32Error = pLoad->read( &ui32Id, sizeof( ui32Id ) );
				ui32Error = pMesh->load( pLoad );
				pMesh->set_id( ui32Id );

				MeshC*	pParent = 0;
				if( ui32ParentId != 0xffffffff ) {
					for( uint32 i = 0; i < m_rScenegraph.size(); i++ ) {
						pParent = find_parent( m_rScenegraph[i], ui32ParentId );
						if( pParent )
							break;
					}
				}

				if( pParent )
					pParent->add_child( pMesh );
				else
					m_rScenegraph.push_back( pMesh );
			}
			break;

		default:
			assert( 0 );
		}

		pLoad->close_chunk();

		if( ui32Error != IO_OK && ui32Error != IO_END ) {
			return ui32Error;
		}
	}

	return ui32Error;
}

//////////////////////////////////////////////////////////////////////////
//
// The transform gizmo
//

TransformGizmoC::TransformGizmoC()
{
	init();
}

TransformGizmoC::TransformGizmoC( EffectI* pParent, uint32 ui32Id ) :
	GizmoI( pParent, ui32Id )
{
	init();
}

TransformGizmoC::TransformGizmoC( EditableI* pOriginal ) :
	GizmoI( pOriginal )
{
	// do not init in clone constructor.
}

TransformGizmoC::~TransformGizmoC()
{
	if( get_original() )
		return;

	m_pParamPos->release();
	m_pParamScale->release();
}

void
TransformGizmoC::init()
{
	set_name( "Transform" );

	m_pParamPos = ParamVector2C::create_new( this, "Position", Vector2C(), ID_TRANSFORM_POS,
		PARAM_STYLE_EDITBOX | PARAM_STYLE_ABS_POSITION, PARAM_ANIMATABLE );
	m_pParamScale = ParamVector2C::create_new( this, "Scale", Vector2C( 1, 1 ), ID_TRANSFORM_SCALE,
		PARAM_STYLE_EDITBOX | PARAM_STYLE_PERCENT, PARAM_ANIMATABLE, Vector2C(), Vector2C(), 0.01f );
}


TransformGizmoC*
TransformGizmoC::create_new( EffectI* pParent, uint32 ui32Id )
{
	return new TransformGizmoC( pParent, ui32Id );
}

DataBlockI*
TransformGizmoC::create()
{
	return new TransformGizmoC;
}

DataBlockI*
TransformGizmoC::create( EditableI* pOriginal )
{
	return new TransformGizmoC( pOriginal );
}

void
TransformGizmoC::copy( EditableI* pEditable )
{
	// must call the base class method
	GizmoI::copy( pEditable );
	TransformGizmoC*	pGizmo = (TransformGizmoC*)pEditable;
	m_pParamPos->copy( pGizmo->m_pParamPos );
	m_pParamScale->copy( pGizmo->m_pParamScale );
}

void
TransformGizmoC::restore( EditableI* pEditable )
{
	GizmoI::restore( pEditable );
	TransformGizmoC*	pGizmo = (TransformGizmoC*)pEditable;
	m_pParamPos = pGizmo->m_pParamPos;
	m_pParamScale = pGizmo->m_pParamScale;
}

int32
TransformGizmoC::get_parameter_count()
{
	return TRANSFORM_COUNT;
}

ParamI*
TransformGizmoC::get_parameter( int32 i32Index )
{
	switch( i32Index ) {
	case ID_TRANSFORM_POS:
		return m_pParamPos; break;
	case ID_TRANSFORM_SCALE:
		return m_pParamScale; break;
	}
	return 0;
}

Vector2C
TransformGizmoC::get_pos( int32 i32Time )
{
	Vector2C	rVal;
	m_pParamPos->get_val( i32Time, rVal );
	return rVal;
}

Vector2C
TransformGizmoC::get_scale( int32 i32Time )
{
	Vector2C	rVal;
	m_pParamScale->get_val( i32Time, rVal );
	return rVal;
}


enum TransformGizmoChunksE {
	CHUNK_TRANSFORMGIZMO_BASE				= 0x1000,
	CHUNK_TRANSFORMGIZMO_PARAM_POS			= 0x2000,
	CHUNK_TRANSFORMGIZMO_PARAM_SCALE		= 0x3000,
};

const uint32	TRANSFORMGIZMO_VERSION = 1;

uint32
TransformGizmoC::save( SaveC* pSave )
{
	uint32	ui32Error = IO_OK;

	// GizmoI stuff
	pSave->begin_chunk( CHUNK_TRANSFORMGIZMO_BASE, TRANSFORMGIZMO_VERSION );
		ui32Error = GizmoI::save( pSave );
	pSave->end_chunk();

	// position
	pSave->begin_chunk( CHUNK_TRANSFORMGIZMO_PARAM_POS, TRANSFORMGIZMO_VERSION );
		ui32Error = m_pParamPos->save( pSave );
	pSave->end_chunk();

	// scale
	pSave->begin_chunk( CHUNK_TRANSFORMGIZMO_PARAM_SCALE, TRANSFORMGIZMO_VERSION );
		ui32Error = m_pParamScale->save( pSave );
	pSave->end_chunk();

	return ui32Error;
}

uint32
TransformGizmoC::load( LoadC* pLoad )
{
	uint32	ui32Error = IO_OK;

	while( (ui32Error = pLoad->open_chunk()) == IO_OK ) {

		switch( pLoad->get_chunk_id() ) {
		case CHUNK_TRANSFORMGIZMO_BASE:
			if( pLoad->get_chunk_version() == TRANSFORMGIZMO_VERSION )
				ui32Error = GizmoI::load( pLoad );
			break;

		case CHUNK_TRANSFORMGIZMO_PARAM_POS:
			if( pLoad->get_chunk_version() == TRANSFORMGIZMO_VERSION )
				ui32Error = m_pParamPos->load( pLoad );
			break;

		case CHUNK_TRANSFORMGIZMO_PARAM_SCALE:
			if( pLoad->get_chunk_version() == TRANSFORMGIZMO_VERSION )
				ui32Error = m_pParamScale->load( pLoad );
			break;

		default:
			assert( 0 );
		}

		pLoad->close_chunk();

		if( ui32Error != IO_OK && ui32Error != IO_END )
			return ui32Error;
	}

	return ui32Error;
}


//////////////////////////////////////////////////////////////////////////
//
// The attribute gizmo
//

AttributeGizmoC::AttributeGizmoC()
{
	init();
}

AttributeGizmoC::AttributeGizmoC( EffectI* pParent, uint32 ui32Id ) :
	GizmoI( pParent, ui32Id )
{
	init();
}

	AttributeGizmoC::AttributeGizmoC( EditableI* pOriginal ) :
	GizmoI( pOriginal )
{
	// do not init in clone constructor.
}

AttributeGizmoC::~AttributeGizmoC()
{
	if( get_original() )
		return;

	m_pParamSize->release();;
	m_pParamCamera->release();;
	m_pParamClear->release();;
	m_pParamClearColor->release();;
	m_pParamFile->release();;
}

void
AttributeGizmoC::init()
{
	set_name( "Attributes" );

	m_pParamSize = ParamVector2C::create_new( this, "Size", Vector2C( 320, 200 ), ID_ATTRIBUTE_SIZE,
		PARAM_STYLE_EDITBOX, PARAM_ANIMATABLE, Vector2C(), Vector2C(), 1 );

	m_pParamCamera = ParamIntC::create_new( this, "Camera", 0, ID_ATTRIBUTE_CAMERA,
		PARAM_STYLE_COMBOBOX, PARAM_ANIMATABLE, 0, 1 );

	m_pParamClear = ParamIntC::create_new( this, "Clear", 1, ID_ATTRIBUTE_CLEAR,
		PARAM_STYLE_COMBOBOX, PARAM_NOT_ANIMATABLE, 0, 1 );
	m_pParamClear->add_label( 0, "Off" );
	m_pParamClear->add_label( 1, "On" );

	m_pParamClearColor = ParamColorC::create_new( this, "Clear Color", ColorC( 0, 0, 0, 1 ), ID_ATTRIBUTE_CLEARCOLOR,
		PARAM_STYLE_COLORPICKER, PARAM_ANIMATABLE );

	m_pParamFile = ParamFileC::create_new( this, "Scene", NULL_SUPERCLASS, CLASS_ASE_IMPORT, ID_ATTRIBUTE_FILE );
}

AttributeGizmoC*
AttributeGizmoC::create_new( EffectI* pParent, uint32 ui32Id )
{
	return new AttributeGizmoC( pParent, ui32Id );
}

DataBlockI*
AttributeGizmoC::create()
{
	return new AttributeGizmoC;
}

DataBlockI*
AttributeGizmoC::create( EditableI* pOriginal )
{
	return new AttributeGizmoC( pOriginal );
}

void
AttributeGizmoC::copy( EditableI* pEditable )
{
	GizmoI::copy( pEditable );

	AttributeGizmoC*	pGizmo = (AttributeGizmoC*)pEditable;
	m_pParamSize->copy( pGizmo->m_pParamSize );
	m_pParamCamera->copy( pGizmo->m_pParamCamera );
	m_pParamClear->copy( pGizmo->m_pParamClear );
	m_pParamClearColor->copy( pGizmo->m_pParamClearColor );
	m_pParamFile->copy( pGizmo->m_pParamFile );
}

void
AttributeGizmoC::restore( EditableI* pEditable )
{
	GizmoI::restore( pEditable );

	AttributeGizmoC*	pGizmo = (AttributeGizmoC*)pEditable;
	m_pParamSize = pGizmo->m_pParamSize;
	m_pParamCamera = pGizmo->m_pParamCamera;
	m_pParamClear = pGizmo->m_pParamClear;
	m_pParamClearColor = pGizmo->m_pParamClearColor;
	m_pParamFile = pGizmo->m_pParamFile;
}

int32
AttributeGizmoC::get_parameter_count()
{
	return ATTRIBUTE_COUNT;
}

ParamI*
AttributeGizmoC::get_parameter( int32 i32Index )
{
	switch( i32Index ) {
	case ID_ATTRIBUTE_SIZE:
		return m_pParamSize; break;
	case ID_ATTRIBUTE_CAMERA:
		return m_pParamCamera; break;
	case ID_ATTRIBUTE_CLEAR:
		return m_pParamClear; break;
	case ID_ATTRIBUTE_CLEARCOLOR:
		return m_pParamClearColor; break;
	case ID_ATTRIBUTE_FILE:
		return m_pParamFile; break;
	}
	return 0;
}

uint32
AttributeGizmoC::update_notify( EditableI* pCaller )
{
	if( pCaller->get_base_class_id() == BASECLASS_GIZMO )
	{
		GizmoI*	pGizmo = (GizmoI*)pCaller;

		if( pGizmo->get_id() == ID_ATTRIBUTE_FILE ) {
			//
			// The file parameter has changed. Update camera labels.
			//

			// Get undo from the changed parameter.
			UndoC*	pUndo = m_pParamFile->get_undo();
			UndoC*	pOldUndo;

			FileHandleC*	pHandle = 0;
			ASEImportC*		pImp = 0;

			pHandle = m_pParamFile->get_file( 0 );
			if( pHandle )
				pImp = (ASEImportC*)pHandle->get_importable();

			if( pImp ) {
				// Apply undo to parameter we change.
				pOldUndo = m_pParamCamera->begin_editing( pUndo );

				// Update camera labels.
				m_pParamCamera->clear_labels();
				for( uint32 i = 0; i < pImp->get_camera_count(); i++ ) {
					CameraC*	pCam = pImp->get_camera( i );
					if( !pCam )
						continue;
					m_pParamCamera->add_label( i, pCam->get_name() );
				}
				m_pParamCamera->set_min_max( 0, pImp->get_camera_count() );

				// End undo block.
				m_pParamCamera->end_editing( pOldUndo );
			}
		}
	}

	return GizmoI::update_notify( pCaller );
}

Vector2C
AttributeGizmoC::get_size( int32 i32Time )
{
	Vector2C	rVal;
	m_pParamSize->get_val( i32Time, rVal );
	return rVal;
}


CameraC*
AttributeGizmoC::get_camera( int32 i32Time )
{
	FileHandleC*	pHandle = 0;
	ASEImportC*		pImp = 0;

	pHandle = m_pParamFile->get_file( i32Time );

	if( pHandle )
		pImp = (ASEImportC*)pHandle->get_importable();

	if( pImp ) {
		int32	i32Val;
		m_pParamCamera->get_val( i32Time, i32Val );
		return pImp->get_camera( i32Val );
	}
	
	return 0;
}

bool
AttributeGizmoC::get_clear( int32 i32Time )
{
	int32	i32Val;
	m_pParamClear->get_val( i32Time, i32Val );
	return i32Val ? true : false;
}

ColorC
AttributeGizmoC::get_clear_color( int32 i32Time )
{
	ColorC	rColor;
	m_pParamClearColor->get_val( i32Time, rColor );
	return rColor;
}

ASEImportC*
AttributeGizmoC::get_file( int32 i32Time )
{
	FileHandleC*	pHandle = 0;
	int32			i32FileTime = 0;
	
	// Get file handle, and file evaluation time.
	// The evaluation time is calculated from the file time offset and time scale.
	m_pParamFile->get_file( i32Time, pHandle, i32FileTime );
	
	if( pHandle ) {
		// Get importable.
		ASEImportC*	pImp = (ASEImportC*)pHandle->get_importable();
		pImp->eval_state( i32FileTime );
		return pImp;
	}
	return 0;
}

enum AttributeGizmoChunksE {
	CHUNK_ATTRIBUTEGIZMO_BASE				= 0x1000,
	CHUNK_ATTRIBUTEGIZMO_PARAM_SIZE			= 0x2000,
	CHUNK_ATTRIBUTEGIZMO_PARAM_CAMERA		= 0x4000,
	CHUNK_ATTRIBUTEGIZMO_PARAM_CLEAR		= 0x5000,
	CHUNK_ATTRIBUTEGIZMO_PARAM_CLEARCOLOR	= 0x6000,
	CHUNK_ATTRIBUTEGIZMO_PARAM_FILE			= 0x7000,
};

const uint32	ATTRIBUTEGIZMO_VERSION = 1;

uint32
AttributeGizmoC::save( SaveC* pSave )
{
	uint32	ui32Error = IO_OK;

	// GizmoI stuff
	pSave->begin_chunk( CHUNK_ATTRIBUTEGIZMO_BASE, ATTRIBUTEGIZMO_VERSION );
		ui32Error = GizmoI::save( pSave );
	pSave->end_chunk();

	// size
	pSave->begin_chunk( CHUNK_ATTRIBUTEGIZMO_PARAM_SIZE, ATTRIBUTEGIZMO_VERSION );
		ui32Error = m_pParamSize->save( pSave );
	pSave->end_chunk();

	// camera
	pSave->begin_chunk( CHUNK_ATTRIBUTEGIZMO_PARAM_CAMERA, ATTRIBUTEGIZMO_VERSION );
		ui32Error = m_pParamCamera->save( pSave );
	pSave->end_chunk();

	// clear
	pSave->begin_chunk( CHUNK_ATTRIBUTEGIZMO_PARAM_CLEAR, ATTRIBUTEGIZMO_VERSION );
		ui32Error = m_pParamClear->save( pSave );
	pSave->end_chunk();

	// clear color
	pSave->begin_chunk( CHUNK_ATTRIBUTEGIZMO_PARAM_CLEARCOLOR, ATTRIBUTEGIZMO_VERSION );
		ui32Error = m_pParamClearColor->save( pSave );
	pSave->end_chunk();

	// file
	pSave->begin_chunk( CHUNK_ATTRIBUTEGIZMO_PARAM_FILE, ATTRIBUTEGIZMO_VERSION );
		ui32Error = m_pParamFile->save( pSave );
	pSave->end_chunk();

	return ui32Error;
}

uint32
AttributeGizmoC::load( LoadC* pLoad )
{
	uint32	ui32Error = IO_OK;

	while( (ui32Error = pLoad->open_chunk()) == IO_OK ) {

		switch( pLoad->get_chunk_id() ) {
		case CHUNK_ATTRIBUTEGIZMO_BASE:
			if( pLoad->get_chunk_version() == ATTRIBUTEGIZMO_VERSION )
				ui32Error = GizmoI::load( pLoad );
			break;

		case CHUNK_ATTRIBUTEGIZMO_PARAM_SIZE:
			if( pLoad->get_chunk_version() == ATTRIBUTEGIZMO_VERSION )
				ui32Error = m_pParamSize->load( pLoad );
			break;

		case CHUNK_ATTRIBUTEGIZMO_PARAM_CAMERA:
			if( pLoad->get_chunk_version() == ATTRIBUTEGIZMO_VERSION )
				ui32Error = m_pParamCamera->load( pLoad );
			break;

		case CHUNK_ATTRIBUTEGIZMO_PARAM_CLEAR:
			if( pLoad->get_chunk_version() == ATTRIBUTEGIZMO_VERSION )
				ui32Error = m_pParamClear->load( pLoad );
			break;

		case CHUNK_ATTRIBUTEGIZMO_PARAM_CLEARCOLOR:
			if( pLoad->get_chunk_version() == ATTRIBUTEGIZMO_VERSION )
				ui32Error = m_pParamClearColor->load( pLoad );
			break;

		case CHUNK_ATTRIBUTEGIZMO_PARAM_FILE:
			if( pLoad->get_chunk_version() == ATTRIBUTEGIZMO_VERSION )
				ui32Error = m_pParamFile->load( pLoad );
			break;

		default:
			assert( 0 );
		}

		pLoad->close_chunk();

		if( ui32Error != IO_OK && ui32Error != IO_END )
			return ui32Error;
	}

	return ui32Error;
}


//////////////////////////////////////////////////////////////////////////
//
// The engine effect
//

EngineEffectC::EngineEffectC()
{
	m_pTransGizmo = TransformGizmoC::create_new( this, 0 );
	m_pAttribGizmo = AttributeGizmoC::create_new( this, 0 );
}

EngineEffectC::EngineEffectC( EditableI* pOriginal ) :
	EffectI( pOriginal ),
	m_pTransGizmo( 0 ),
	m_pAttribGizmo( 0 )
{
	// clone constructor. initialize only.
}

EngineEffectC::~EngineEffectC()
{
	if( get_original() )
		return;

	m_pTransGizmo->release();
	m_pAttribGizmo->release();
}

EngineEffectC*
EngineEffectC::create_new()
{
	return new EngineEffectC;
}

DataBlockI*
EngineEffectC::create()
{
	return new EngineEffectC;
}

DataBlockI*
EngineEffectC::create( EditableI* pOriginal )
{
	return new EngineEffectC( pOriginal );
}

void
EngineEffectC::copy( EditableI* pEditable )
{
	EffectI::copy( pEditable );

	EngineEffectC*	pEffect = (EngineEffectC*)pEditable;
	m_pTransGizmo->copy( pEffect->m_pTransGizmo );
	m_pAttribGizmo->copy( pEffect->m_pAttribGizmo );
}

void
EngineEffectC::restore( EditableI* pEditable )
{
	EffectI::restore( pEditable );

	EngineEffectC*	pEffect = (EngineEffectC*)pEditable;
	m_pTransGizmo = pEffect->m_pTransGizmo;
	m_pAttribGizmo = pEffect->m_pAttribGizmo;
}

int32
EngineEffectC::get_gizmo_count()
{
	return GIZMO_COUNT;
}

GizmoI*
EngineEffectC::get_gizmo( int32 i32Index )
{
	// Returns specified gizmo.
	// Since the ID's are zero based, we can use them as indices.
	switch( i32Index ) {
	case ID_GIZMO_TRANS:
		return m_pTransGizmo;
	case ID_GIZMO_ATTRIB:
		return m_pAttribGizmo;
	}
	return 0;
}

ClassIdC
EngineEffectC::get_class_id()
{
	return CLASS_ASEPLAYER_EFFECT;
}

const char*
EngineEffectC::get_class_name()
{
	return "ASE Player";
}

void
EngineEffectC::set_default_file( int32 i32Time, FileHandleC* pHandle )
{
	// Get the file parameter
  ParamFileC*	pParam = (ParamFileC*)m_pAttribGizmo->get_parameter( ID_ATTRIBUTE_FILE );

    // Start editing.
    UndoC*  pOldUndo;
    if( get_undo() ) {
        pOldUndo = pParam->begin_editing( get_undo() );
    }

    // Set the file.
    pParam->set_file( i32Time, pHandle );

    // End editing.
    if( get_undo() ) {
        pParam->end_editing( pOldUndo );
    }
    
}

ParamI*
EngineEffectC::get_default_param( int32 i32Param )
{
	if( i32Param == DEFAULT_PARAM_POSITION )
		return m_pTransGizmo->get_parameter( ID_TRANSFORM_POS );
	else if( i32Param == DEFAULT_PARAM_SCALE )
		return m_pTransGizmo->get_parameter( ID_TRANSFORM_SCALE );
	return 0;
}

void
EngineEffectC::initialize( uint32 ui32Reason, DemoInterfaceC* pInterface )
{
	EffectI::initialize( ui32Reason, pInterface );
}


void
EngineEffectC::eval_state( int32 i32Time )
{
	if( !m_pDemoInterface )
		return;

	DeviceContextC* pContext = m_pDemoInterface->get_device_context();
	TimeContextC* pTimeContext = m_pDemoInterface->get_time_context();

	Matrix2C	rPosMat, rScaleMat, rPivotMat;

	Vector2C	rScale = m_pTransGizmo->get_scale( i32Time );
	Vector2C	rPos = m_pTransGizmo->get_pos( i32Time );

	rPosMat.set_trans( rPos );
	rScaleMat.set_scale( rScale ) ;

	m_rTM = rScaleMat * rPosMat;

	//
	// calc bounding box
	//
	Vector2C	rSize = m_pAttribGizmo->get_size( i32Time ) * 0.5f;
	Vector2C	rMin, rMax;

	rMin = -rSize;
	rMax = rSize;

	rMin *= m_rTM;
	rMax *= m_rTM;

	m_rBBox[0] = rMin;
	m_rBBox[1] = rMax;
	m_rBBox.normalize();


	//
	// Calculate current frame.
	//
	int32		i32CurrentFrame = 0;
	ASEImportC*	pImp = (ASEImportC*)m_pAttribGizmo->get_file( i32Time );

	if( pImp ) {
		i32CurrentFrame = pImp->get_current_frame();
	}

	// set aspectratio
	float32	f32Aspect = 1.0f;
	if( rScale[0] != 0 && rScale[1] != 0 ) {
		f32Aspect = fabs( rScale[0] / rScale[1] );
	}

	ColorC		rClearColor = m_pAttribGizmo->get_clear_color( i32Time );
	bool		bDoClear = m_pAttribGizmo->get_clear( i32Time );
	CameraC*	pCam = m_pAttribGizmo->get_camera( i32Time );


	//
	// Render effect
	//

	uint32	i;
	// Get the OpenGL device.
	OpenGLDeviceC*	pDevice = (OpenGLDeviceC*)pContext->query_interface( CLASS_OPENGL_DEVICEDRIVER );
	if( !pDevice )
		return;

	// Get the OpenGL viewport.
	OpenGLViewportC*	pViewport = (OpenGLViewportC*)pDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );
	if( !pViewport )
		return;

	//
	// setup camera
	//

	if( !pImp )
		return;

	if( !pCam )
		return;

	if( pCam )
		pCam->eval_state( i32CurrentFrame );

	Vector3C	rCamPos, rCamTgt;
	float		f32FOV;

	rCamPos = pCam->get_position();
	rCamTgt = pCam->get_target_position();
	f32FOV = pCam->get_fov();

	pViewport->set_perspective( m_rBBox, f32FOV / M_PI * 180.0f, f32Aspect, pCam->get_near_plane() + 1.0f, pCam->get_far_plane() );

	if( bDoClear ) {
		glClearColor( rClearColor[0], rClearColor[1], rClearColor[2], rClearColor[3] );
		glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	}

	glMatrixMode( GL_MODELVIEW );
	gluLookAt( rCamPos[0], rCamPos[1], rCamPos[2], rCamTgt[0], rCamTgt[1], rCamTgt[2], 0, 1, 0 );


	GLfloat	f32White[] = { 1, 1, 1, 1 };

	glShadeModel( GL_SMOOTH );

	glEnable( GL_NORMALIZE  );
	glEnable( GL_DEPTH_TEST );
	glEnable( GL_BLEND );
	glEnable( GL_LIGHTING );
	glDepthFunc( GL_LEQUAL );
	glEnable( GL_DEPTH_TEST );
	glEnable( GL_ALPHA_TEST );
	glAlphaFunc( GL_GREATER, 0.1f );
	glCullFace( GL_BACK );
	glEnable( GL_CULL_FACE );
	glLightModeli( GL_LIGHT_MODEL_TWO_SIDE , 0 );
	glBlendFunc( GL_ONE, GL_ZERO );


	for( i = 0; i < pImp->get_light_count(); i++ ) {
		LightC*	pLight = pImp->get_light( i );

		pLight->eval_state( i32CurrentFrame );

		Vector3C	rPos = pLight->get_position();
		ColorC		rColor = pLight->get_color();
		float32		f32Mult = pLight->get_multiplier();
		GLfloat		f32Diffuse[] = { 1, 1, 1, 1 };
		GLfloat		f32Pos[] = { 1, 1, 1, 1 };

		f32Diffuse[0] = rColor[0] * f32Mult;
		f32Diffuse[1] = rColor[1] * f32Mult;
		f32Diffuse[2] = rColor[2] * f32Mult;

		f32Pos[0] = rPos[0];
		f32Pos[1] = rPos[1];
		f32Pos[2] = rPos[2];

		glLightf( GL_LIGHT0 + i, GL_CONSTANT_ATTENUATION, 1 );
		glLightf( GL_LIGHT0 + i, GL_LINEAR_ATTENUATION , 0 );
		glLightf( GL_LIGHT0 + i, GL_QUADRATIC_ATTENUATION , 0 );

		glEnable( GL_LIGHT0 + i );
		glLightfv( GL_LIGHT0 + i, GL_DIFFUSE, f32Diffuse );
		glLightfv( GL_LIGHT0 + i, GL_SPECULAR, f32White );
		glLightfv( GL_LIGHT0 + i, GL_POSITION , f32Pos );
	}

	glEnable( GL_LIGHTING );

	for( i = 0; i < pImp->get_scenegraphitem_count(); i++ )
		render_item( i32Time, pDevice, pContext, pTimeContext, pImp->get_scenegraphitem( i ), i32CurrentFrame );

	for( i = 0; i < pImp->get_light_count(); i++ )
		glDisable( GL_LIGHT0 + i );

	glDisable( GL_CULL_FACE );
	glDisable( GL_LIGHTING );
	glDisable( GL_NORMALIZE  );
	glDisable( GL_ALPHA_TEST );
}


void
EngineEffectC::render_item( int32 i32Time, OpenGLDeviceC* pDevice, DeviceContextC* pContext, TimeContextC* pTimeContext, MeshC* pMesh, int32 i32Frame )
{
	if( !pMesh )
		return;

	pMesh->eval_state( i32Frame );

	glPushMatrix();

	float32		f32X, f32Y, f32Z, f32A;
	Vector3C	rPos = pMesh->get_position();
	QuatC		rRot = pMesh->get_rotation();
	Vector3C	rScale = pMesh->get_scale();
	QuatC		rScaleRot = pMesh->get_scale_rotation();
	QuatC		rInvScaleRot = rScaleRot.unit_inverse();


	// Strangle kludge. The animated rotations doesnt work correctly
	// if the angle is not negated when the quat is set to OpenGL.

	glTranslatef( rPos[0], rPos[1], rPos[2] );

	rRot.to_axis_angle( f32X, f32Y, f32Z, f32A );
	glRotatef( -f32A / (float32)M_PI * 180.0f, f32X, f32Y, f32Z );

	rScaleRot.to_axis_angle( f32X, f32Y, f32Z, f32A );
	glRotatef( -f32A / (float32)M_PI * 180.0f, f32X, f32Y, f32Z );

	glScalef( rScale[0], rScale[1], rScale[2] );

	rInvScaleRot.to_axis_angle( f32X, f32Y, f32Z, f32A );
	glRotatef( -f32A / (float32)M_PI * 180.0f, f32X, f32Y, f32Z );


	//
	// draw
	//

	float32	f32Amb[4];
	float32	f32Diff[4];
	float32	f32Spec[4];

	ColorC	rAmbient = pMesh->get_ambient();
	ColorC	rDiffuse = pMesh->get_diffuse();
	ColorC	rSpecular = pMesh->get_specular();

	f32Amb[0] = rAmbient[0] * 0.25;
	f32Amb[1] = rAmbient[1] * 0.25;
	f32Amb[2] = rAmbient[2] * 0.25;
	f32Amb[3] = 1;
	glMaterialfv( GL_FRONT_AND_BACK, GL_AMBIENT, f32Amb );

	f32Diff[0] = rDiffuse[0];
	f32Diff[1] = rDiffuse[1];
	f32Diff[2] = rDiffuse[2];
	f32Diff[3] = 1;
	glMaterialfv( GL_FRONT_AND_BACK, GL_DIFFUSE, f32Diff );

	f32Spec[0] = rSpecular[0];
	f32Spec[1] = rSpecular[1];
	f32Spec[2] = rSpecular[2];
	f32Spec[3] = 1;
	glMaterialfv( GL_FRONT_AND_BACK, GL_SPECULAR, f32Spec );

	glMateriali( GL_FRONT_AND_BACK, GL_SHININESS, 2 + (int32)(pMesh->get_shininess() * 126.0f) );
	
	// Texture stuff

	ImportableImageI*	pTex = 0;
	FileHandleC*		pHandle = pMesh->get_texture();
	bool				bHasTex = false;

	if( pHandle )
		pTex = (ImportableImageI*)pHandle->get_importable();

	if( pTex && pMesh->get_texcoord_count()  ) {
		glEnable( GL_TEXTURE_2D );
		pTex->eval_state( i32Time );
		pTex->bind_texture( pDevice, IMAGE_WRAP | IMAGE_LINEAR );
		bHasTex = true;
	}
	else
		glDisable( GL_TEXTURE_2D );

	glBegin( GL_TRIANGLES );

	for( uint32 i = 0; i < pMesh->get_index_count(); i++ ) {

		Vector3C	rVert = pMesh->get_vert( pMesh->get_index( i ) );
		Vector3C	rNorm = pMesh->get_norm( pMesh->get_index( i ) );

		glNormal3f( rNorm[0], rNorm[1], rNorm[2] );

		if( bHasTex ) {
			Vector2C	rTexcoord = pMesh->get_texcoord( pMesh->get_texcoord_index( i ) );
			glTexCoord2f( rTexcoord[0], rTexcoord[1] );
		}

		glVertex3f( rVert[0], rVert[1], rVert[2] );
	}

	glEnd();

	glDisable( GL_TEXTURE_2D );


	// draw childs
	for( i = 0; i < pMesh->get_child_count(); i++ )
		render_item( i32Time, pDevice, pContext, pTimeContext, (MeshC*)pMesh->get_child( i ), i32Frame );

	glPopMatrix();

}


BBox2C
EngineEffectC::get_bbox()
{
	return m_rBBox;
}

const Matrix2C&
EngineEffectC::get_transform_matrix() const
{
	return m_rTM;
}

bool
EngineEffectC::hit_test( const Vector2C& rPoint )
{
	return m_rBBox.contains( rPoint );
}

enum EngineEffectChunksE {
	CHUNK_ENGINEEFFECT_BASE =			0x1000,
	CHUNK_ENGINEEFFECT_TRANSGIZMO =		0x2000,
	CHUNK_ENGINEEFFECT_ATTRIBGIZMO =	0x3000,
};

const uint32	ENGINEEFFECT_VERSION = 1;

uint32
EngineEffectC::save( SaveC* pSave )
{
	uint32	ui32Error = IO_OK;

	// EffectI stuff
	pSave->begin_chunk( CHUNK_ENGINEEFFECT_BASE, ENGINEEFFECT_VERSION );
		ui32Error = EffectI::save( pSave );
	pSave->end_chunk();

	// transform
	pSave->begin_chunk( CHUNK_ENGINEEFFECT_TRANSGIZMO, ENGINEEFFECT_VERSION );
		ui32Error = m_pTransGizmo->save( pSave );
	pSave->end_chunk();

	// attribute
	pSave->begin_chunk( CHUNK_ENGINEEFFECT_ATTRIBGIZMO, ENGINEEFFECT_VERSION );
		ui32Error = m_pAttribGizmo->save( pSave );
	pSave->end_chunk();

	return ui32Error;
}

uint32
EngineEffectC::load( LoadC* pLoad )
{
	uint32	ui32Error = IO_OK;

	while( (ui32Error = pLoad->open_chunk()) == IO_OK ) {

		switch( pLoad->get_chunk_id() ) {
		case CHUNK_ENGINEEFFECT_BASE:
			if( pLoad->get_chunk_version() == ENGINEEFFECT_VERSION )
				ui32Error = EffectI::load( pLoad );
			break;

		case CHUNK_ENGINEEFFECT_TRANSGIZMO:
			if( pLoad->get_chunk_version() == ENGINEEFFECT_VERSION )
				ui32Error = m_pTransGizmo->load( pLoad );
			break;

		case CHUNK_ENGINEEFFECT_ATTRIBGIZMO:
			if( pLoad->get_chunk_version() == ENGINEEFFECT_VERSION )
				ui32Error = m_pAttribGizmo->load( pLoad );
			break;

		default:
			assert( 0 );
		}

		pLoad->close_chunk();

		if( ui32Error != IO_OK && ui32Error != IO_END )
			return ui32Error;
	}

	return ui32Error;
}


