//
// ImagePlugin.cpp
//
// Image Plugin
//
// Copyright (c) 2000-2003 memon/moppi productions
//

#define WIN32_LEAN_AND_MEAN		// Exclude rarely-used stuff from Windows headers
#include <windows.h>
#include <gl\gl.h>
#include <gl\glu.h>

#include "glext.h"

// Demopaja headers
#include "DemopajaVersion.h"
#include "PajaTypes.h"
#include "ClassDescC.h"
#include "ImportableI.h"
#include "OpenGLDeviceC.h"
#include "OpenGLViewportC.h"
#include "ImagePlugin.h"
#include "FileIO.h"
#include "AutoGizmoC.h"
#include "ImportableImageI.h"
#include "ImageResampleC.h"
#include "EffectMaskI.h"
#include "minilzo.h"

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

using namespace ImagePlugin;




static
void
TRACE( const char* szFormat, ...  )
{
	char	szMsg[256];
	va_list	rList;
	va_start( rList, szFormat );
	_vsnprintf( szMsg, 255, szFormat, rList );
	va_end( rList );
	OutputDebugString( szMsg );
}

static
bool
CHECK_GL_ERROR( const char* szName )
{
	GLenum	eError = glGetError();
	if( eError ) {
		OutputDebugString( szName );
		OutputDebugString( " " );
		OutputDebugString( (const char*)gluErrorString( eError ) );
		OutputDebugString( "\n" );
//		TRACE_LOG( "%s %s", szName, (const char*)gluErrorString( eError ) );
		return true;
	}
	return false;
}


//
// OpenGL extensions
//

namespace ImagePlugin
{

// Multitexture
static bool							g_bMultiTexture = false;
PFNGLMULTITEXCOORD1FARBPROC		glMultiTexCoord1fARB		= NULL;
PFNGLMULTITEXCOORD2FARBPROC		glMultiTexCoord2fARB		= NULL;
PFNGLMULTITEXCOORD2FVARBPROC	glMultiTexCoord2fvARB		= NULL;
PFNGLMULTITEXCOORD3FARBPROC		glMultiTexCoord3fARB		= NULL;
PFNGLMULTITEXCOORD4FARBPROC		glMultiTexCoord4fARB		= NULL;
PFNGLACTIVETEXTUREARBPROC			glActiveTextureARB			= NULL;
PFNGLCLIENTACTIVETEXTUREARBPROC	glClientActiveTextureARB	= NULL;	

static bool							g_bBlendEqu = false;
PFNGLBLENDEQUATIONPROC			glBlendEquation = NULL;
PFNGLBLENDCOLORPROC				glBlendColor = NULL;


void
init_gl_extensions()
{
	if( !g_bMultiTexture ) {

		char*	szExtensions;	
		szExtensions = (char*)glGetString( GL_EXTENSIONS );

		if( szExtensions && strstr( szExtensions, "GL_ARB_multitexture" ) != 0 &&
			strstr( szExtensions, "GL_EXT_texture_env_combine" ) != 0 ) {	

			glMultiTexCoord1fARB	 = (PFNGLMULTITEXCOORD1FARBPROC)wglGetProcAddress( "glMultiTexCoord1fARB" );
			glMultiTexCoord2fARB	 = (PFNGLMULTITEXCOORD2FARBPROC)wglGetProcAddress( "glMultiTexCoord2fARB" );
			glMultiTexCoord2fvARB	 = (PFNGLMULTITEXCOORD2FVARBPROC)wglGetProcAddress( "glMultiTexCoord2fvARB" );
			glMultiTexCoord3fARB	 = (PFNGLMULTITEXCOORD3FARBPROC)wglGetProcAddress( "glMultiTexCoord3fARB" );
			glMultiTexCoord4fARB	 = (PFNGLMULTITEXCOORD4FARBPROC)wglGetProcAddress( "glMultiTexCoord4fARB" );
			glActiveTextureARB		 = (PFNGLACTIVETEXTUREARBPROC)wglGetProcAddress( "glActiveTextureARB" );
			glClientActiveTextureARB = (PFNGLCLIENTACTIVETEXTUREARBPROC)wglGetProcAddress( "glClientActiveTextureARB" );
			g_bMultiTexture = true;
		}
		else
		{
			TRACE( "no multi texturing\n" );
		}
	}

	if( !g_bBlendEqu ) {
		char*	szExtensions;	
		szExtensions = (char*)glGetString( GL_EXTENSIONS );

		if( szExtensions &&
			strstr( szExtensions, "GL_EXT_blend_color" ) != 0 &&
			strstr( szExtensions, "GL_EXT_blend_minmax" ) != 0 &&
			strstr( szExtensions, "GL_EXT_blend_subtract" ) != 0 ) {	

			glBlendEquation			 = (PFNGLBLENDEQUATIONPROC)wglGetProcAddress( "glBlendEquationEXT" );
			glBlendColor			 = (PFNGLBLENDCOLORPROC)wglGetProcAddress( "glBlendColorEXT" );

			g_bBlendEqu = true;
		}
		else
		{
			TRACE( "no blend equ\n" );
		}
	}
}

};

//
// Helper functions
//

inline
uint32
lowest_bit_mask( uint32 v )
{
	return (v & -v);
}

static
uint32
ceil_power2( uint32 ui32Num )
{
	uint32	i = lowest_bit_mask( ui32Num );
	while( i < ui32Num )
		i <<= 1;
	return i;
}

static
int32
nearest_power2( int32 i32Num )
{
	int32	i = ceil_power2( i32Num );	// bigger
	int32	j = i / 2;					// smaller

	int32	i32DiffI = i - i32Num;
	int32	i32DiffJ = i32Num - j;

	// diff J has to be twice as little as diffI to be chosen.
	i32DiffI /= 2;

	if( i32DiffJ < i32DiffI )
		return j;

	return i;
}


//////////////////////////////////////////////////////////////////////////
//
//  TGA importer class descriptor.
//

TGAImportDescC::TGAImportDescC()
{
	// empty
}

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

void*
TGAImportDescC::create()
{
	return TGAImportC::create_new();
}

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

SuperClassIdC
TGAImportDescC::get_super_class_id() const
{
	return SUPERCLASS_IMAGE;
}

ClassIdC
TGAImportDescC::get_class_id() const
{
	return CLASS_TGA_IMPORT;
}

const char*
TGAImportDescC::get_name() const
{
	return "TGA Image";
}

const char*
TGAImportDescC::get_desc() const
{
	return "Importer for Targa (.TGA) images";
}

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

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

const char*
TGAImportDescC::get_url() const
{
	return "http://www.demopaja.org";
}

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

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

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

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

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


//////////////////////////////////////////////////////////////////////////
//
//  Image effect class descriptor.
//

ImageDescC::ImageDescC()
{
	// empty
}

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

void*
ImageDescC::create()
{
	return (void*)ImageEffectC::create_new();
}

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

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

ClassIdC
ImageDescC::get_class_id() const
{
	return CLASS_IMAGE_EFFECT;
};

const char*
ImageDescC::get_name() const
{
	return "Image";
}

const char*
ImageDescC::get_desc() const
{
	return "Image Effect";
}

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

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

const char*
ImageDescC::get_url() const
{
	return "http://www.demopaja.org/";
}

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

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

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


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

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


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

TGAImportDescC	g_rTGAImportDesc;
ImageDescC	g_rImageDesc;

#ifndef DEMOPAJAPLAYER

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

BOOL APIENTRY
DllMain( HANDLE hModule, DWORD ulReasonForCall, LPVOID lpReserved )
{
    switch( ulReasonForCall )
	{
		case DLL_PROCESS_ATTACH:
		case DLL_THREAD_ATTACH:
		case DLL_THREAD_DETACH:
		case DLL_PROCESS_DETACH:
			break;
    }
    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_rTGAImportDesc;
	if( i == 1 )
		return &g_rImageDesc;
	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 "ImagePlugin.dll - Image Plugin (c)2000-2003 memon/moppi productions";
}

#endif



//////////////////////////////////////////////////////////////////////////
//
// The effect
//


uint32	ImageEffectC::m_ui32WhiteTextureID = 0;
uint32	ImageEffectC::m_ui32BlackTextureID = 0;
int32		ImageEffectC::m_i32TextureRefCount = 0;


ImageEffectC::ImageEffectC() :
	m_bTextureRef( false )
{
	//
	// Create Transform gizmo.
	//
	m_pTraGizmo = AutoGizmoC::create_new( this, "Transform", ID_GIZMO_TRANS );

	m_pTraGizmo->add_parameter(	ParamVector2C::create_new( m_pTraGizmo, "Position", Vector2C(), ID_TRANSFORM_POS,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_ABS_POSITION, PARAM_ANIMATABLE ) );
	
	m_pTraGizmo->add_parameter(	ParamVector2C::create_new( m_pTraGizmo, "Pivot", Vector2C(), ID_TRANSFORM_PIVOT,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_REL_POSITION | PARAM_STYLE_WORLD_SPACE, PARAM_ANIMATABLE ) );
	
	m_pTraGizmo->add_parameter(	ParamFloatC::create_new( m_pTraGizmo, "Rotation", 0, ID_TRANSFORM_ROT,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_ANGLE, PARAM_ANIMATABLE, 0, 0, 1.0f ) );

	m_pTraGizmo->add_parameter(	ParamVector2C::create_new( m_pTraGizmo, "Scale", Vector2C( 1, 1 ), ID_TRANSFORM_SCALE,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_PERCENT, PARAM_ANIMATABLE, Vector2C(), Vector2C(), 0.01f ) );


	//
	// Create Attributes gizmo.
	//

	m_pAttGizmo = AutoGizmoC::create_new( this, "Attributes", ID_GIZMO_ATTRIB );

	// Image
	m_pAttGizmo->add_parameter(	ParamFileC::create_new( m_pAttGizmo, "Image", SUPERCLASS_IMAGE, NULL_CLASSID, ID_ATTRIBUTE_IMAGE, PARAM_STYLE_FILE, PARAM_ANIMATABLE ) );

	// Scale
	m_pAttGizmo->add_parameter(	ParamVector2C::create_new( m_pAttGizmo, "Scale", Vector2C( 1, 1 ), ID_ATTRIBUTE_SCALE,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_PERCENT, PARAM_ANIMATABLE, Vector2C(), Vector2C(), 0.01f ) );

	// Offset
	m_pAttGizmo->add_parameter(	ParamVector2C::create_new( m_pAttGizmo, "Offset", Vector2C(), ID_ATTRIBUTE_OFFSET,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_REL_POSITION | PARAM_STYLE_OBJECT_SPACE, PARAM_ANIMATABLE ) );

	// Wrap
	ParamIntC*	pWrapMode = ParamIntC::create_new( m_pAttGizmo, "Wrap", 0, ID_ATTRIBUTE_WRAP, PARAM_STYLE_COMBOBOX, PARAM_ANIMATABLE, 0, 1 );
	pWrapMode->add_label( 0, "Clamp" );
	pWrapMode->add_label( 1, "Repeat" );
	m_pAttGizmo->add_parameter( pWrapMode );

	// Resample
	ParamIntC*	pResampleMode = ParamIntC::create_new( m_pAttGizmo, "Resample", 0, ID_ATTRIBUTE_RESAMPLE, PARAM_STYLE_COMBOBOX, PARAM_ANIMATABLE, 0, 1 );
	pResampleMode->add_label( 0, "Bilinear" );
	pResampleMode->add_label( 1, "Nearest" );
	m_pAttGizmo->add_parameter( pResampleMode );

	// Color
	m_pAttGizmo->add_parameter(	ParamColorC::create_new( m_pAttGizmo, "Color", ColorC( 1, 1, 1, 1 ), ID_ATTRIBUTE_COLOR,
							PARAM_STYLE_COLORPICKER_RGB, PARAM_ANIMATABLE ) );

	// Opacity
	m_pAttGizmo->add_parameter(	ParamFloatC::create_new( m_pAttGizmo, "Opacity", 1.0f, ID_ATTRIBUTE_OPACITY,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_PERCENT, PARAM_ANIMATABLE, 0, 1.0f, 0.01f ) );

	// Blend
	ParamIntC*	pBlendMode = ParamIntC::create_new( m_pAttGizmo, "Blend", 0, ID_ATTRIBUTE_BLEND, PARAM_STYLE_COMBOBOX, PARAM_ANIMATABLE, 0, 6 );
	pBlendMode->add_label( 0, "Normal" );
	pBlendMode->add_label( 1, "Replace" );
	pBlendMode->add_label( 2, "Add" );
	pBlendMode->add_label( 3, "Mult" );
	pBlendMode->add_label( 4, "Sub" );
	pBlendMode->add_label( 5, "Lighten" );
	pBlendMode->add_label( 6, "Darken" );
	m_pAttGizmo->add_parameter( pBlendMode );

	// Mask
	m_pAttGizmo->add_parameter(	ParamLinkC::create_new( m_pAttGizmo, "Mask", SUPERCLASS_EFFECT_MASK, NULL_CLASSID,
						ID_ATTRIBUTE_MASK, PARAM_STYLE_SINGLE_LINK ) );
}

ImageEffectC::ImageEffectC( EditableI* pOriginal ) :
	EffectI( pOriginal ),
	m_pTraGizmo( 0 ),
	m_pAttGizmo( 0 ),
	m_bTextureRef( false )
{
	// Empty. The parameters are not created in the clone constructor.
}

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

	if( m_bTextureRef )
	{
		release_textures();
		m_bTextureRef = false;
	}

	// Release gizmos.
	m_pTraGizmo->release();
	m_pAttGizmo->release();
}

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

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

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

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

	// Make deep copy.
	ImageEffectC*	pEffect = (ImageEffectC*)pEditable;
	m_pTraGizmo->copy( pEffect->m_pTraGizmo );
	m_pAttGizmo->copy( pEffect->m_pAttGizmo );
}

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

	// Make shallow copy.
	ImageEffectC*	pEffect = (ImageEffectC*)pEditable;
	m_pTraGizmo = pEffect->m_pTraGizmo;
	m_pAttGizmo = pEffect->m_pAttGizmo;
}

int32
ImageEffectC::get_gizmo_count()
{
	// Return number of gizmos inside this effect.
	return GIZMO_COUNT;
}

GizmoI*
ImageEffectC::get_gizmo( PajaTypes::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_pTraGizmo;
	case ID_GIZMO_ATTRIB:
		return m_pAttGizmo;
	}

	return 0;
}

ClassIdC
ImageEffectC::get_class_id()
{
	// Return the class ID. Should be same as in the class descriptor.
	return CLASS_IMAGE_EFFECT;
}

const char*
ImageEffectC::get_class_name()
{
	// Return the class name. Should be same as in the class descriptor.
	return "Image";
}

void
ImageEffectC::set_default_file( int32 i32Time, FileHandleC* pHandle )
{
	// Sets the default file.

	// Get the file parameter.
	ParamFileC*	pParam = (ParamFileC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_IMAGE );

	// Begin Undo block.
	UndoC*	pOldUndo = pParam->begin_editing( get_undo() );
		// Set the file.
		pParam->set_file( i32Time, pHandle );
	// End undo block.
	pParam->end_editing( pOldUndo );
}

ParamI*
ImageEffectC::get_default_param( int32 i32Param )
{
	// Return specified default parameter.
	if( i32Param == DEFAULT_PARAM_POSITION )
		return m_pTraGizmo->get_parameter( ID_TRANSFORM_POS );
	else if( i32Param == DEFAULT_PARAM_ROTATION )
		return m_pTraGizmo->get_parameter( ID_TRANSFORM_ROT );
	else if( i32Param == DEFAULT_PARAM_SCALE )
		return m_pTraGizmo->get_parameter( ID_TRANSFORM_SCALE );
	else if( i32Param == DEFAULT_PARAM_PIVOT )
		return m_pTraGizmo->get_parameter( ID_TRANSFORM_PIVOT );
	return 0;
}

uint32
ImageEffectC::update_notify( EditableI* pCaller )
{
	return PARAM_NOTIFY_NONE;
}

void
ImageEffectC::initialize( uint32 ui32Reason, PajaSystem::DemoInterfaceC* pInterface )
{
	EffectI::initialize( ui32Reason, pInterface );

	init_gl_extensions();

	init_textures();
}

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

	// Requires multitexturing and bledn equation.
	if( !g_bMultiTexture || !g_bBlendEqu )
	{
		TRACE( "Not all extensions supported\n" );
		return;
	}

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


	Matrix2C	rPosMat, rRotMat, rScaleMat, rPivotMat;

	Vector2C	rScale;
	Vector2C	rPos;
	Vector2C	rPivot;
	float32		f32Rot;

	// Get parameters which affect the transformation.
	((ParamVector2C*)m_pTraGizmo->get_parameter( ID_TRANSFORM_POS ))->get_val( i32Time, rPos );
	((ParamVector2C*)m_pTraGizmo->get_parameter( ID_TRANSFORM_PIVOT ))->get_val( i32Time, rPivot );
	((ParamFloatC*)m_pTraGizmo->get_parameter( ID_TRANSFORM_ROT ))->get_val( i32Time, f32Rot );
	((ParamVector2C*)m_pTraGizmo->get_parameter( ID_TRANSFORM_SCALE ))->get_val( i32Time, rScale );

	// Calculate transformation matrix.
	rPivotMat.set_trans( rPivot );
	rPosMat.set_trans( rPos );
	rRotMat.set_rot( f32Rot / 180.0f * (float32)M_PI );
	rScaleMat.set_scale( rScale ) ;
	m_rTM = rPivotMat * rRotMat * rScaleMat * rPosMat;

	float32		f32Width = 25;
	float32		f32Height = 25;
	Vector2C	rMin, rMax;
	Vector2C	rVec;


	// Get the size from the file or use the defaults if no file.
	ImportableImageI*	pImp = 0;
	FileHandleC*		pHandle;
	int32				i32FileTime;
	((ParamFileC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_IMAGE ))->get_file( i32Time, pHandle, i32FileTime );

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

	if( pImp ) {
		pImp->eval_state( i32FileTime );
		f32Width = (float32)pImp->get_width() * 0.5f;
		f32Height = (float32)pImp->get_height() * 0.5f;
	}


	// Calcualte vertices of the rectangle.
	m_rVertices[0][0] = -f32Width;		// top-left
	m_rVertices[0][1] = -f32Height;

	m_rVertices[1][0] =  f32Width;		// top-right
	m_rVertices[1][1] = -f32Height;

	m_rVertices[2][0] =  f32Width;		// bottom-right
	m_rVertices[2][1] =  f32Height;

	m_rVertices[3][0] = -f32Width;		// bottom-left
	m_rVertices[3][1] =  f32Height;

	// Calculate bounding box
	for( uint32 i = 0; i < 4; i++ ) {
		rVec = m_rTM * m_rVertices[i];
		m_rVertices[i] = rVec;

		if( !i )
			rMin = rMax = rVec;
		else {
			if( rVec[0] < rMin[0] ) rMin[0] = rVec[0];
			if( rVec[1] < rMin[1] ) rMin[1] = rVec[1];
			if( rVec[0] > rMax[0] ) rMax[0] = rVec[0];
			if( rVec[1] > rMax[1] ) rMax[1] = rVec[1];
		}
	}

	// Store bounding box.
	m_rBBox[0] = rMin;
	m_rBBox[1] = rMax;

	Vector2C	rImageScale( 1, 1 );
	Vector2C	rImageOffset( 0, 0 );
	int32			i32Wrap = WRAPMODE_CLAMP;
	int32			i32Resample = RESAMPLEMODE_BILINEAR;
	int32			i32Blend = BLENDMODE_NORMAL;
	ColorC		rColor( 1, 1, 1 );
	float32		f32Opacity = 1.0f;


	// Get image scale.
	((ParamVector2C*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_SCALE ))->get_val( i32Time, rImageScale );
	// Get image offset.
	((ParamVector2C*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_OFFSET ))->get_val( i32Time, rImageOffset );
	// Get Wrap
	((ParamIntC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_WRAP ))->get_val( i32Time, i32Wrap );
	// Get Resample
	((ParamIntC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_RESAMPLE ))->get_val( i32Time, i32Resample );
	// Get Color.
	((ParamColorC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_COLOR ))->get_val( i32Time, rColor );
	// Get Opacity.
	((ParamFloatC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_OPACITY ))->get_val( i32Time, f32Opacity );
	// Get Blend mode.
	((ParamIntC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_BLEND ))->get_val( i32Time, i32Blend );


	// Mask
	ParamLinkC*				pMaskParam = (ParamLinkC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_MASK );
	EffectMaskI*			pMaskEffect = (EffectMaskI*)pMaskParam->get_link( 0 );
	ImportableImageI*	pMaskImp = 0;
	int32							i32MaskImageFlags = 0;
	Matrix2C					rMaskMat;
	if( pMaskEffect )
	{
		pMaskImp = pMaskEffect->get_mask_image();
		i32MaskImageFlags = pMaskEffect->get_mask_image_flags();
		rMaskMat = pMaskEffect->get_mask_matrix();
	}


	// 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;

	// Set orthographic projection.
	pViewport->set_ortho( m_rBBox, m_rBBox[0][0], m_rBBox[1][0], m_rBBox[1][1], m_rBBox[0][1] );


	int	i32TextureUnit = 0;

	// If there is image set it as current texture.
	int32	i32TextureFlags = 0;

	if( i32Wrap == WRAPMODE_CLAMP )
		i32TextureFlags |= IMAGE_CLAMP;
	else
		i32TextureFlags |= IMAGE_WRAP;

	if( i32Resample == RESAMPLEMODE_BILINEAR )
		i32TextureFlags |= IMAGE_LINEAR;
	else
		i32TextureFlags |= IMAGE_NEAREST;


	glDisable( GL_DEPTH_TEST );
	glDepthMask( GL_FALSE );
	glEnable( GL_BLEND );

	bool	bResetBlendEqu = false;

	int32	i32BaseUnit = -1;
	int32	i32MaskUnit = -1;
	int32	i32TempUnit = -1;

	rColor[3] = f32Opacity;

	if( i32Blend == BLENDMODE_NORMAL ) {

		// First texture stage is the image to show
		if( pImp ) {
			glActiveTextureARB( GL_TEXTURE0_ARB + i32TextureUnit );
			pImp->bind_texture( pDevice, i32TextureUnit, i32TextureFlags );
			glEnable( GL_TEXTURE_2D );
			glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
			i32BaseUnit = i32TextureUnit;
			i32TextureUnit++;
		}
		if( pMaskImp ) {
			glActiveTextureARB( GL_TEXTURE0_ARB + i32TextureUnit );
			pMaskImp->bind_texture( pDevice, i32TextureUnit, i32MaskImageFlags );
			glEnable( GL_TEXTURE_2D );

			// Pass color from previous texture stage
			glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT );
			glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_REPLACE );
			glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT );
			glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR );

			// Modulate alpha
			glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_MODULATE );
			glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT, GL_TEXTURE );
			glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_EXT, GL_SRC_ALPHA );
			glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_EXT, GL_PREVIOUS_EXT );
			glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_EXT, GL_SRC_ALPHA );

			glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );

			i32MaskUnit = i32TextureUnit;
			i32TextureUnit++;
		}

		// Normal
		glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );

		// Set color and opacity
		glColor4fv( rColor );
	}
	if( i32Blend == BLENDMODE_REPLACE ) {

		// First texture stage is the image to show
		if( pImp ) {
			glActiveTextureARB( GL_TEXTURE0_ARB + i32TextureUnit );
			pImp->bind_texture( pDevice, i32TextureUnit, i32TextureFlags );
			glEnable( GL_TEXTURE_2D );

			glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );

/*			// Modulate color from previous texture stage
			glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT );
			glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE );
			glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_TEXTURE );
			glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR );
			glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_PREVIOUS_EXT );
			glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR );

			// Pass alpha alpha
			glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_REPLACE );
			glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT, GL_PREVIOUS_EXT );
			glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_EXT, GL_SRC_ALPHA );*/

			i32BaseUnit = i32TextureUnit;
			i32TextureUnit++;
		}
		if( pMaskImp ) {
			glActiveTextureARB( GL_TEXTURE0_ARB + i32TextureUnit );
			pMaskImp->bind_texture( pDevice, i32TextureUnit, i32MaskImageFlags );
			glEnable( GL_TEXTURE_2D );

			// Pass color from previous texture stage
			glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT );
			glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_REPLACE );
			glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT );
			glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR );

			// Modulate alpha
			glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_MODULATE );
			glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT, GL_TEXTURE );
			glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_EXT, GL_SRC_ALPHA );
			glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_EXT, GL_PREVIOUS_EXT );
			glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_EXT, GL_SRC_ALPHA );

			i32MaskUnit = i32TextureUnit;
			i32TextureUnit++;
		}

		// Normal
//		glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
		glBlendFunc( GL_ONE, GL_ZERO );

		// Set color and opacity
		glColor4fv( rColor );
	}
	else if( i32Blend == BLENDMODE_ADD ) {

		// First texture stage is the image to show
		if( pImp ) {
			glActiveTextureARB( GL_TEXTURE0_ARB + i32TextureUnit );
			pImp->bind_texture( pDevice, i32TextureUnit, i32TextureFlags );
			glEnable( GL_TEXTURE_2D );
			glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
			i32BaseUnit = i32TextureUnit;
			i32TextureUnit++;
		}
		if( pMaskImp ) {
			glActiveTextureARB( GL_TEXTURE0_ARB + i32TextureUnit );
			pMaskImp->bind_texture( pDevice, i32TextureUnit, i32MaskImageFlags );
			glEnable( GL_TEXTURE_2D );

			// Pass color from previous texture stage
			glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT );
			glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_REPLACE );
			glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT );
			glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR );

			// Modulate alpha
			glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_MODULATE );
			glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT, GL_TEXTURE );
			glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_EXT, GL_SRC_ALPHA );
			glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_EXT, GL_PREVIOUS_EXT );
			glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_EXT, GL_SRC_ALPHA );

			i32MaskUnit = i32TextureUnit;
			i32TextureUnit++;
		}

		// Add
		glBlendFunc( GL_SRC_ALPHA, GL_ONE );

		// Set color and opacity
		glColor4fv( rColor );
	}
	else if( i32Blend == BLENDMODE_MULT ) {
		// Mult
		if( pImp ) {
			glActiveTextureARB( GL_TEXTURE0_ARB + i32TextureUnit );
			pImp->bind_texture( pDevice, i32TextureUnit, i32TextureFlags );
			glEnable( GL_TEXTURE_2D );
			glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
			i32BaseUnit = i32TextureUnit;
			i32TextureUnit++;
		}

		if( pMaskImp ) {
			glActiveTextureARB( GL_TEXTURE0_ARB + i32TextureUnit );
			pMaskImp->bind_texture( pDevice, i32TextureUnit, i32MaskImageFlags );
			glEnable( GL_TEXTURE_2D );

			// Pass color from previous texture stage
			glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT );
			glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_REPLACE );
			glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT );
			glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR );

			// Modulate alpha
			glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_MODULATE );
			glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT, GL_TEXTURE );
			glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_EXT, GL_SRC_ALPHA );
			glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_EXT, GL_PREVIOUS_EXT );
			glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_EXT, GL_SRC_ALPHA );

			i32MaskUnit = i32TextureUnit;
			i32TextureUnit++;
		}

		// Fade to white
		glActiveTextureARB( GL_TEXTURE0_ARB + i32TextureUnit );
		glEnable( GL_TEXTURE_2D );
		glBindTexture( GL_TEXTURE_2D, m_ui32WhiteTextureID );

		// Fade to white texture
		glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT );
		glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_INTERPOLATE_EXT );
		glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT );
		glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR );
		glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE );
		glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR );
		glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE2_RGB_EXT, GL_PREVIOUS_EXT );
		glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND2_RGB_EXT, GL_SRC_ALPHA );

		// Modulate alpha
		glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_REPLACE );
		glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT, GL_PREVIOUS_EXT );
		glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_EXT, GL_SRC_ALPHA );

		i32TempUnit = i32TextureUnit;

		i32TextureUnit++;

		glBlendFunc( GL_DST_COLOR, GL_ZERO );

		// Set color and opacity
		glColor4f( rColor[0], rColor[1], rColor[2], rColor[3] );
	}
	else if( i32Blend == BLENDMODE_SUB ) {
		// Sub
		// First texture stage is the image to show
		if( pImp ) {
			glActiveTextureARB( GL_TEXTURE0_ARB + i32TextureUnit );
			pImp->bind_texture( pDevice, i32TextureUnit, i32TextureFlags );
			glEnable( GL_TEXTURE_2D );
			glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
			i32BaseUnit = i32TextureUnit;
			i32TextureUnit++;
		}
		if( pMaskImp ) {
			glActiveTextureARB( GL_TEXTURE0_ARB + i32TextureUnit );
			pMaskImp->bind_texture( pDevice, i32TextureUnit, i32MaskImageFlags );
			glEnable( GL_TEXTURE_2D );

			// Pass color from previous texture stage
			glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT );
			glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_REPLACE );
			glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT );
			glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR );

			// Modulate alpha
			glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_MODULATE );
			glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT, GL_TEXTURE );
			glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_EXT, GL_SRC_ALPHA );
			glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_EXT, GL_PREVIOUS_EXT );
			glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_EXT, GL_SRC_ALPHA );

			i32MaskUnit = i32TextureUnit;
			i32TextureUnit++;
		}

		glBlendEquation( GL_FUNC_REVERSE_SUBTRACT_EXT );
		glBlendFunc( GL_SRC_ALPHA, GL_ONE );

		// Set color and opacity
		glColor4fv( rColor );
	}
	else if( i32Blend == BLENDMODE_LIGHTEN ) {
		// Lighten
		if( pImp ) {
			glActiveTextureARB( GL_TEXTURE0_ARB + i32TextureUnit );
			pImp->bind_texture( pDevice, i32TextureUnit, i32TextureFlags );
			glEnable( GL_TEXTURE_2D );
			glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
			i32BaseUnit = i32TextureUnit;
			i32TextureUnit++;
		}

		if( pMaskImp ) {
			glActiveTextureARB( GL_TEXTURE0_ARB + i32TextureUnit );
			pMaskImp->bind_texture( pDevice, i32TextureUnit, i32MaskImageFlags );
			glEnable( GL_TEXTURE_2D );

			// Pass color from previous texture stage
			glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT );
			glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_REPLACE );
			glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT );
			glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR );

			// Modulate alpha
			glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_MODULATE );
			glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT, GL_TEXTURE );
			glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_EXT, GL_SRC_ALPHA );
			glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_EXT, GL_PREVIOUS_EXT );
			glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_EXT, GL_SRC_ALPHA );

			i32MaskUnit = i32TextureUnit;
			i32TextureUnit++;
		}

		// Fade to white
		glActiveTextureARB( GL_TEXTURE0_ARB + i32TextureUnit );
		glEnable( GL_TEXTURE_2D );
		glBindTexture( GL_TEXTURE_2D, m_ui32BlackTextureID );

		// Fade to white texture
		glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT );
		glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_INTERPOLATE_EXT );
		glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT );
		glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR );
		glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE );
		glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR );
		glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE2_RGB_EXT, GL_PREVIOUS_EXT );
		glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND2_RGB_EXT, GL_SRC_ALPHA );

		// Modulate alpha
		glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_REPLACE );
		glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT, GL_PREVIOUS_EXT );
		glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_EXT, GL_SRC_ALPHA );

		i32TempUnit = i32TextureUnit;

		i32TextureUnit++;

		glBlendEquation( GL_MAX );
		glBlendFunc( GL_ONE, GL_ONE );

		// Set color and opacity
		glColor4f( rColor[0], rColor[1], rColor[2], rColor[3] );
	}
	else if( i32Blend == BLENDMODE_DARKEN ) {
		// Darken
		// Lighten
		if( pImp ) {
			glActiveTextureARB( GL_TEXTURE0_ARB + i32TextureUnit );
			pImp->bind_texture( pDevice, i32TextureUnit, i32TextureFlags );
			glEnable( GL_TEXTURE_2D );
			glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
			i32BaseUnit = i32TextureUnit;
			i32TextureUnit++;
		}

		if( pMaskImp ) {
			glActiveTextureARB( GL_TEXTURE0_ARB + i32TextureUnit );
			pMaskImp->bind_texture( pDevice, i32TextureUnit, i32MaskImageFlags );
			glEnable( GL_TEXTURE_2D );

			// Pass color from previous texture stage
			glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT );
			glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_REPLACE );
			glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT );
			glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR );

			// Modulate alpha
			glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_MODULATE );
			glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT, GL_TEXTURE );
			glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_EXT, GL_SRC_ALPHA );
			glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_EXT, GL_PREVIOUS_EXT );
			glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_EXT, GL_SRC_ALPHA );

			i32MaskUnit = i32TextureUnit;
			i32TextureUnit++;
		}

		// Fade to white
		glActiveTextureARB( GL_TEXTURE0_ARB + i32TextureUnit );
		glEnable( GL_TEXTURE_2D );
		glBindTexture( GL_TEXTURE_2D, m_ui32WhiteTextureID );

		// Fade to white texture
		glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT );
		glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_INTERPOLATE_EXT );
		glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT );
		glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_RGB_EXT, GL_SRC_COLOR );
		glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE );
		glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND1_RGB_EXT, GL_SRC_COLOR );
		glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE2_RGB_EXT, GL_PREVIOUS_EXT );
		glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND2_RGB_EXT, GL_SRC_ALPHA );

		// Modulate alpha
		glTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_ALPHA_EXT, GL_REPLACE );
		glTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_EXT, GL_PREVIOUS_EXT );
		glTexEnvi( GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_EXT, GL_SRC_ALPHA );

		i32TempUnit = i32TextureUnit;

		i32TextureUnit++;

		glBlendEquation( GL_MIN );
		glBlendFunc( GL_ONE, GL_ONE );

		// Set color and opacity
		glColor4f( rColor[0], rColor[1], rColor[2], rColor[3] );
	}

	// Default texture coordinates
	Vector2C	rTexCoords[4] = { Vector2C( 0, 0 ), Vector2C( 1, 0 ), Vector2C( 1, 1 ), Vector2C( 0, 1 ) };
	Vector2C	rMaskTexCoords[4];

	rImageOffset[0] /= f32Width * 2.0f;
	rImageOffset[1] /= f32Height * 2.0f;

	// Transform texture coords
	for( i = 0; i < 4; i++ )
	{
		// Image Tex coords
		rTexCoords[i] = (rTexCoords[i] - Vector2C( 0.5f, 0.5f ) - rImageOffset) * rImageScale + Vector2C( 0.5f, 0.5f );

		// Mask Texcoords
		rMaskTexCoords[i] = m_rVertices[i] * rMaskMat;
	}

	// Draw rectangle.
	glBegin( GL_QUADS );

	if( i32BaseUnit != -1 )
		glMultiTexCoord2fARB( i32BaseUnit, rTexCoords[0][0], rTexCoords[0][1] );
	if( i32MaskUnit != -1 )
		glMultiTexCoord2fARB( i32MaskUnit, rMaskTexCoords[0][0], rMaskTexCoords[0][1] );
	if( i32TempUnit != -1 )
		glMultiTexCoord2fARB( i32TempUnit, rTexCoords[0][0], rTexCoords[0][1] );
	glVertex2f( m_rVertices[0][0], m_rVertices[0][1] );
	
	if( i32BaseUnit != -1 )
		glMultiTexCoord2fARB( i32BaseUnit, rTexCoords[1][0], rTexCoords[1][1] );
	if( i32MaskUnit != -1 )
		glMultiTexCoord2fARB( i32MaskUnit, rMaskTexCoords[1][0], rMaskTexCoords[1][1] );
	if( i32TempUnit != -1 )
		glMultiTexCoord2fARB( i32TempUnit, rTexCoords[1][0], rTexCoords[1][1] );
	glVertex2f( m_rVertices[1][0], m_rVertices[1][1] );
	
	if( i32BaseUnit != -1 )
		glMultiTexCoord2fARB( i32BaseUnit, rTexCoords[2][0], rTexCoords[2][1] );
	if( i32MaskUnit != -1 )
		glMultiTexCoord2fARB( i32MaskUnit, rMaskTexCoords[2][0], rMaskTexCoords[2][1] );
	if( i32TempUnit != -1 )
		glMultiTexCoord2fARB( i32TempUnit, rTexCoords[2][0], rTexCoords[2][1] );
	glVertex2f( m_rVertices[2][0], m_rVertices[2][1] );

	if( i32BaseUnit != -1 )
		glMultiTexCoord2fARB( i32BaseUnit, rTexCoords[3][0], rTexCoords[3][1] );
	if( i32MaskUnit != -1 )
		glMultiTexCoord2fARB( i32MaskUnit, rMaskTexCoords[3][0], rMaskTexCoords[3][1] );
	if( i32TempUnit != -1 )
		glMultiTexCoord2fARB( i32TempUnit, rTexCoords[3][0], rTexCoords[3][1] );
	glVertex2f( m_rVertices[3][0], m_rVertices[3][1] );

	glEnd();


	// Reset blend equ
	glBlendEquation( GL_FUNC_ADD );

	// Reset to default texture unit
	for( i = 0; i < i32TextureUnit; i++ )
	{
		glActiveTextureARB( GL_TEXTURE0_ARB + i );
		glDisable( GL_TEXTURE_2D );
		glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
	}

	glDepthMask( GL_TRUE );
}

void
ImageEffectC::init_textures()
{
	if( m_i32TextureRefCount == 0 )
	{
		TRACE( "init textures\n" );

		uint8	ui8WhiteTextureData[(4 * 4) * 3] = {
			255, 255, 255,	255, 255, 255,	255, 255, 255,	255, 255, 255,
			255, 255, 255,	255, 255, 255,	255, 255, 255,	255, 255, 255,
			255, 255, 255,	255, 255, 255,	255, 255, 255,	255, 255, 255,
			255, 255, 255,	255, 255, 255,	255, 255, 255,	255, 255, 255,
		};

		uint8	ui8BlackTextureData[(4 * 4) * 3] = {
			0, 0, 0,	0, 0, 0,	0, 0, 0,	0, 0, 0,
			0, 0, 0,	0, 0, 0,	0, 0, 0,	0, 0, 0,
			0, 0, 0,	0, 0, 0,	0, 0, 0,	0, 0, 0,
			0, 0, 0,	0, 0, 0,	0, 0, 0,	0, 0, 0,
		};

		// Create textures
		glGenTextures( 1, &m_ui32WhiteTextureID );
		glBindTexture( GL_TEXTURE_2D, m_ui32WhiteTextureID );
		glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
		glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, 4, 4, 0, GL_RGB, GL_UNSIGNED_BYTE, ui8WhiteTextureData );

		glGenTextures( 1, &m_ui32BlackTextureID );
		glBindTexture( GL_TEXTURE_2D, m_ui32BlackTextureID );
		glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
		glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, 4, 4, 0, GL_RGB, GL_UNSIGNED_BYTE, ui8BlackTextureData );
	}

	m_i32TextureRefCount++;

	m_bTextureRef = true;
}

void
ImageEffectC::release_textures()
{
	m_i32TextureRefCount--;
	if( m_i32TextureRefCount == 0 )
	{
		// Delete textures
		if( m_ui32WhiteTextureID )
			glDeleteTextures( 1, &m_ui32WhiteTextureID );
		if( m_ui32BlackTextureID )
			glDeleteTextures( 1, &m_ui32BlackTextureID );
	}
}

BBox2C
ImageEffectC::get_bbox()
{
	// Return the bounding box.
	return m_rBBox;
}

const Matrix2C&
ImageEffectC::get_transform_matrix() const
{
	// Return the trnasformation matrix.
	return m_rTM;
}

bool
ImageEffectC::hit_test( const Vector2C& rPoint )
{
	// Point in polygon test.
	// from c.g.a FAQ
	int		i, j;
	bool	bInside = false;

	for( i = 0, j = 4 - 1; i < 4; j = i++ ) {
		if( ( ((m_rVertices[i][1] <= rPoint[1]) && (rPoint[1] < m_rVertices[j][1])) ||
			((m_rVertices[j][1] <= rPoint[1]) && (rPoint[1] < m_rVertices[i][1])) ) &&
			(rPoint[0] < (m_rVertices[j][0] - m_rVertices[i][0]) * (rPoint[1] - m_rVertices[i][1]) / (m_rVertices[j][1] - m_rVertices[i][1]) + m_rVertices[i][0]) )

			bInside = !bInside;
	}

	return bInside;
}


enum ImageEffectChunksE {
	CHUNK_IMAGE_BASE =				0x10,
	CHUNK_IMAGE_TRANSGIZMO =	0x20,
	CHUNK_IMAGE_ATTRIBGIZMO =	0x30,
};

const uint32	IMAGE_VERSION = 1;

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

	// EffectI base class
	pSave->begin_chunk( CHUNK_IMAGE_BASE, IMAGE_VERSION );
		ui32Error = EffectI::save( pSave );
	pSave->end_chunk();

	// Transform
	pSave->begin_chunk( CHUNK_IMAGE_TRANSGIZMO, IMAGE_VERSION );
		ui32Error = m_pTraGizmo->save( pSave );
	pSave->end_chunk();

	// Attribute
	pSave->begin_chunk( CHUNK_IMAGE_ATTRIBGIZMO, IMAGE_VERSION );
		ui32Error = m_pAttGizmo->save( pSave );
	pSave->end_chunk();

	return ui32Error;
}

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

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

		switch( pLoad->get_chunk_id() ) {
		case CHUNK_IMAGE_BASE:
			// EffectI base class
			if( pLoad->get_chunk_version() == IMAGE_VERSION )
				ui32Error = EffectI::load( pLoad );
			break;

		case CHUNK_IMAGE_TRANSGIZMO:
			// Transform
			if( pLoad->get_chunk_version() == IMAGE_VERSION )
				ui32Error = m_pTraGizmo->load( pLoad );
			break;

		case CHUNK_IMAGE_ATTRIBGIZMO:
			// Attribute
			if( pLoad->get_chunk_version() == IMAGE_VERSION )
				ui32Error = m_pAttGizmo->load( pLoad );
			break;

		default:
			assert( 0 );
		}

		pLoad->close_chunk();

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

	return ui32Error;
}


//////////////////////////////////////////////////////////////////////////
//
// TGA Importer class implementation.
//

TGAImportC::TGAImportC() :
	m_ui32TextureId( 0 ),
	m_pData( 0 ),
	m_i32Width( 0 ),
	m_i32Height( 0 ),
	m_i32Bpp( 0 )
{
	m_rTexBounds[0] = Vector2C( 0, 0 );
	m_rTexBounds[1] = Vector2C( 1, 1 );
}

TGAImportC::TGAImportC( EditableI* pOriginal ) :
	ImportableImageI( pOriginal ),
	m_ui32TextureId( 0 ),
	m_pData( 0 ),
	m_i32Width( 0 ),
	m_i32Height( 0 ),
	m_i32Bpp( 0 )
{
	m_rTexBounds[0] = Vector2C( 0, 0 );
	m_rTexBounds[1] = Vector2C( 1, 1 );
}

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

	// Delete creted texture.
	if( m_ui32TextureId )
		glDeleteTextures( 1, &m_ui32TextureId );

	delete [] m_pData;
}

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

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

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

void
TGAImportC::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
TGAImportC::restore( EditableI* pEditable )
{
	TGAImportC*	pFile = (TGAImportC*)pEditable;

	m_ui32TextureId = pFile->m_ui32TextureId;
	m_sFileName = pFile->m_sFileName;
	m_pData = pFile->m_pData;
	m_i32Width = pFile->m_i32Width;
	m_i32Height = pFile->m_i32Height;
	m_i32Bpp = pFile->m_i32Bpp;
}

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

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



// Load an 8-bit gray TGA file

bool
TGAImportC::load_8bit_pal_TGA( FILE* fp, TGAHeaderS& rHdr )
{

	m_pData = new uint8[m_i32Width * m_i32Height * 3];
	m_i32Bpp = 24;

	//-- Bypass the image identification field, if present -------------------
	if( rHdr.idlen ) {
		if( fseek( fp,(long)rHdr.idlen, SEEK_CUR )!= 0 )
			return false;
	}

	if( !rHdr.cmlen )
		return false;

	uint8	ui8Pal[256 * 3];
	uint8	ui8PalTmp[256 * 4];
	memset( ui8Pal, 0, 256 * 3 );


	// Load up the palette
	switch( rHdr.cmes ) {
	case 15:
	case 16:
		{
			if( !fread( ui8PalTmp, rHdr.cmlen * 2, 1, fp ) )
				return false;

			uint16*	pPal = (uint16*)ui8PalTmp;

			for( int32 i = 0; i < rHdr.cmlen; i++ ) {
				ui8Pal[i * 3 + 0] = (*pPal & 0x7c00) >> 7;
				ui8Pal[i * 3 + 1] = (*pPal & 0x03e0) >> 2;
				ui8Pal[i * 3 + 2] = (*pPal & 0x001f) << 3;
				pPal++;
			}
		}
		break;

	case 24:
		{
			if( !fread( ui8PalTmp, rHdr.cmlen * 3, 1, fp ) )
				return false;

			for( int32 i = 0; i < rHdr.cmlen; i++ ) {
				ui8Pal[i * 3 + 0] = ui8PalTmp[i * 3 + 2];
				ui8Pal[i * 3 + 1] = ui8PalTmp[i * 3 + 1];
				ui8Pal[i * 3 + 2] = ui8PalTmp[i * 3 + 0];
			}
		}
		break;

	case 32:
		{
			if( !fread( ui8PalTmp, rHdr.cmlen * 4, 1, fp ) )
				return false;

			for( int32 i = 0; i < rHdr.cmlen; i++ ) {
				ui8Pal[i * 3 + 0] = ui8PalTmp[i * 4 + 2];
				ui8Pal[i * 3 + 1] = ui8PalTmp[i * 4 + 1];
				ui8Pal[i * 3 + 2] = ui8PalTmp[i * 4 + 0];
			}
		}
		break;
	}

	//-- Read Image File -----------------------------------------------------
	switch( rHdr.imgtype ) {

	//-- Uncompressed Palette -----------------------------
	case 1:
		{
			uint8*	pDest = m_pData;
			uint8	ui8Pixel;
			for( int32 int32Y = 0; int32Y < m_i32Height; int32Y++ ) {

				for( int32 i32X = 0; i32X < m_i32Width; i32X++ ) {
					if( !fread( &ui8Pixel, 1, 1, fp ) )
						return false;
					ui8Pixel -= rHdr.cmorg;
					pDest[i32X * 3 + 0] = ui8Pal[ui8Pixel * 3 + 0];
					pDest[i32X * 3 + 1] = ui8Pal[ui8Pixel * 3 + 1];
					pDest[i32X * 3 + 2] = ui8Pal[ui8Pixel * 3 + 2];
				}

				pDest += m_i32Width * 3;
			}
		}
		break;
	
	//-- Compressed Palette -------------------------------

	case 9:
		{
			int32	i32X = 0;
			int32	i32Y = 0;
			uint8	ui8Rle;
			uint8	ui8Pixel;
			uint8*	pDest = m_pData;
			
			while( 1 ) {
				
				if( !fread( &ui8Rle, 1, 1, fp ) )
					return false;
				
				if( ui8Rle > 127 ) {
					//-- Compressed Block
					ui8Rle -= 127;

					if( !fread( &ui8Pixel, 1, 1, fp ) )
						return false;

					for( int32 i = 0; i < ui8Rle; i++ ) {

						ui8Pixel -= rHdr.cmorg;
						pDest[i32X++] = ui8Pal[ui8Pixel * 3 + 0];
						pDest[i32X++] = ui8Pal[ui8Pixel * 3 + 1];
						pDest[i32X++] = ui8Pal[ui8Pixel * 3 + 2];

						if( i32X >= m_i32Width * 3 ) {
							i32X = 0;
							i32Y++;
							if( i32Y >= m_i32Height )
								break;
							pDest += m_i32Width * 3;
						}
					}
				}
				else {
					//-- Uncompressed Block
					ui8Rle++;

					for( int32 i = 0; i < ui8Rle; i++ ) {

						if( !fread( &ui8Pixel, 1, 1, fp ) )
							return false;

						pDest[i32X++] = ui8Pal[ui8Pixel * 3 + 0];
						pDest[i32X++] = ui8Pal[ui8Pixel * 3 + 1];
						pDest[i32X++] = ui8Pal[ui8Pixel * 3 + 2];

						if( i32X >= m_i32Width * 3 ) {
							i32X = 0;
							i32Y++;
							if( i32Y >= m_i32Height )
								break;
							pDest += m_i32Width * 3;
						}
					}
				}
			}
		}
		break;
	}

	return true;
}



// Load an 8-bit gray TGA file

bool
TGAImportC::load_8bit_gray_TGA( FILE* fp, TGAHeaderS& rHdr )
{

	m_pData = new uint8[m_i32Width * m_i32Height];
	m_i32Bpp = 8;

	//-- Bypass the image identification field, if present -------------------
	if( rHdr.idlen ) {
		if( fseek( fp,(long)rHdr.idlen, SEEK_CUR )!= 0 )
			return false;
	}

	//-- Bypass the color map, if present ------------------------------------
	if( rHdr.cmlen ) {
		if( fseek( fp, (long)rHdr.cmlen * (long)((rHdr.cmes + 7) / 8), SEEK_CUR ) != 0 )
			return false;
	}

	//-- Read Image File -----------------------------------------------------
	switch( rHdr.imgtype ) {

	//-- Uncompressed Gray -----------------------------
	case 3:
		{
			uint8*	pDest = m_pData;
			for( int32 int32Y = 0; int32Y < m_i32Height; int32Y++ ) {

				if( !fread( pDest, m_i32Width, 1, fp ) )
					return false;

				pDest += m_i32Width;
			}
		}
		break;
	
	//-- Compressed Gray -------------------------------

	case 11:
		{
			int32	i32X = 0;
			int32	i32Y = 0;
			uint8	ui8Rle;
			uint8	ui8Pixel;
			uint8*	pDest = m_pData;
			
			while( 1 ) {
				
				if( !fread( &ui8Rle, 1, 1, fp ) )
					return false;
				
				if( ui8Rle > 127 ) {
					//-- Compressed Block
					ui8Rle -= 127;

					if( !fread( &ui8Pixel, 1, 1, fp ) )
						return false;

					for( int32 i = 0; i < ui8Rle; i++ ) {

						pDest[i32X++] = ui8Pixel;

						if( i32X >= m_i32Width ) {
							i32X = 0;
							i32Y++;
							if( i32Y >= m_i32Height )
								break;
							pDest += m_i32Width;
						}
					}
				}
				else {
					//-- Uncompressed Block
					ui8Rle++;

					for( int32 i = 0; i < ui8Rle; i++ ) {

						if( !fread( &ui8Pixel, 1, 1, fp ) )
							return false;

						pDest[i32X++] = ui8Pixel;

						if( i32X >= m_i32Width ) {
							i32X = 0;
							i32Y++;
							if( i32Y >= m_i32Height )
								break;
							pDest += m_i32Width;
						}
					}
				}
			}
		}
		break;
	}

	return true;
}

// Load an 16-bit TGA file

bool
TGAImportC::load_16bit_TGA( FILE* fp, TGAHeaderS& rHdr )
{

	m_pData = new uint8[m_i32Width * m_i32Height * 4];
	m_i32Bpp = 32;

	//-- Bypass the image identification field, if present -------------------
	if( rHdr.idlen ) {
		if( fseek( fp,(long)rHdr.idlen, SEEK_CUR )!= 0 )
			return false;
	}

	//-- Bypass the color map, if present ------------------------------------
	if( rHdr.cmlen ) {
		if( fseek( fp, (long)rHdr.cmlen * (long)((rHdr.cmes + 7) / 8), SEEK_CUR ) != 0 )
			return false;
	}

//	OutputDebugString( "16bit ok\n" );

	//-- Read Image File -----------------------------------------------------
	switch( rHdr.imgtype ) {

	//-- Uncompressed RGB -----------------------------
	case 2:
		{
			uint8*	pDest = m_pData;
			for( int32 int32Y = 0; int32Y < m_i32Height; int32Y++ ) {
				uint16	ui16Pixel;
				for( int32 i32X = 0; i32X < m_i32Width; i32X++ ) {
					if( !fread( &ui16Pixel, 2, 1, fp ) )
						return false;
					pDest[i32X * 4 + 0] = (ui16Pixel & 0x7c00) >> 7;
					pDest[i32X * 4 + 1] = (ui16Pixel & 0x03e0) >> 2;
					pDest[i32X * 4 + 2] = (ui16Pixel & 0x001f) << 3;
					pDest[i32X * 4 + 3] = (ui16Pixel & 0x8000) ? 0 : 0xff;
				}
				pDest += m_i32Width * 4;
			}
		}
		break;
	
	//-- Compressed RGB -------------------------------

	case 10:
		{
			int32	i32X = 0;
			int32	i32Y = 0;
			uint8	ui8Rle;
			uint16	ui16Pixel;
			uint8*	pDest = m_pData;
			
			while( 1 ) {
				
				if( !fread( &ui8Rle, 1, 1, fp ) )
					return false;
				
				if( ui8Rle > 127 ) {
					//-- Compressed Block
					ui8Rle -= 127;

					if( !fread( &ui16Pixel, 2, 1, fp ) )
						return false;

					for( int32 i = 0; i < ui8Rle; i++ ) {

						pDest[i32X++] = (ui16Pixel & 0x7c00) >> 7;
						pDest[i32X++] = (ui16Pixel & 0x03e0) >> 2;
						pDest[i32X++] = (ui16Pixel & 0x001f) << 3;
						pDest[i32X++] = (ui16Pixel & 0x8000) ? 0 : 0xff;

						if( i32X >= m_i32Width * 4 ) {
							i32X = 0;
							i32Y++;
							if( i32Y >= m_i32Height )
								break;
							pDest += m_i32Width * 4;
						}
					}
				}
				else {
					//-- Uncompressed Block
					ui8Rle++;

					for( int32 i = 0; i < ui8Rle; i++ ) {

						if( !fread( &ui16Pixel, 2, 1, fp ) )
							return false;

						pDest[i32X++] = (ui16Pixel & 0x7c00) >> 7;
						pDest[i32X++] = (ui16Pixel & 0x03e0) >> 2;
						pDest[i32X++] = (ui16Pixel & 0x001f) << 3;
						pDest[i32X++] = (ui16Pixel & 0x8000) ? 0xff : 0;

						if( i32X >= m_i32Width * 4 ) {
							i32X = 0;
							i32Y++;
							if( i32Y >= m_i32Height )
								break;
							pDest += m_i32Width * 4;
						}
					}
				}
			}
		}
		break;
	}

	return true;
}

// Load an 24-bit TGA file

bool
TGAImportC::load_24bit_TGA( FILE* fp, TGAHeaderS& rHdr )
{

	m_pData = new uint8[m_i32Width * m_i32Height *3];
	m_i32Bpp = 24;

	//-- Bypass the image identification field, if present -------------------
	if( rHdr.idlen ) {
		if( fseek( fp,(long)rHdr.idlen, SEEK_CUR )!= 0 )
			return false;
	}

	//-- Bypass the color map, if present ------------------------------------
	if( rHdr.cmlen ) {
		if( fseek( fp, (long)rHdr.cmlen * (long)((rHdr.cmes + 7) / 8), SEEK_CUR ) != 0 )
			return false;
	}

	//-- Read Image File -----------------------------------------------------
	switch( rHdr.imgtype ) {

	//-- Uncompressed RGB -----------------------------
	case 2:
		{
			uint8*	pDest = m_pData;
			for( int32 int32Y = 0; int32Y < m_i32Height; int32Y++ ) {
				if( !fread( pDest, m_i32Width * 3, 1, fp ) )
					return false;

				// swap r & b
				for( int32 i = 0; i < m_i32Width; i++ ) {
					uint8	ui8Tmp = pDest[i * 3];
					pDest[i * 3] = pDest[i * 3 + 2];
					pDest[i * 3 + 2] = ui8Tmp;
				}

				pDest += m_i32Width * 3;
			}
		}
		break;
	
	//-- Compressed RGB -------------------------------

	case 10:
		{
			int32	i32X = 0;
			int32	i32Y = 0;
			uint8	ui8Rle;
			uint8	ui8Pixel[3];
			uint8*	pDest = m_pData;
			
			while( 1 ) {
				
				if( !fread( &ui8Rle, 1, 1, fp ) )
					return false;
				
				if( ui8Rle > 127 ) {
					//-- Compressed Block
					ui8Rle -= 127;

					if( !fread( ui8Pixel, 3, 1, fp ) )
						return false;

					for( int32 i = 0; i < ui8Rle; i++ ) {
						pDest[i32X++] = ui8Pixel[2];
						pDest[i32X++] = ui8Pixel[1];
						pDest[i32X++] = ui8Pixel[0];
						if( i32X >= m_i32Width * 3 ) {
							i32X = 0;
							i32Y++;
							if( i32Y >= m_i32Height )
								break;
							pDest += m_i32Width * 3;
						}
					}
				}
				else {
					//-- Uncompressed Block
					ui8Rle++;

					for( int32 i = 0; i < ui8Rle; i++ ) {

						if( !fread( ui8Pixel, 3, 1, fp ) )
							return false;

						pDest[i32X++] = ui8Pixel[2];
						pDest[i32X++] = ui8Pixel[1];
						pDest[i32X++] = ui8Pixel[0];

						if( i32X >= m_i32Width * 3 ) {
							i32X = 0;
							i32Y++;
							if( i32Y >= m_i32Height )
								break;
							pDest += m_i32Width * 3;
						}
					}
				}
			}
		}
		break;
	}

	return true;
}



// Load an 32-bit TGA file

bool
TGAImportC::load_32bit_TGA( FILE* fp, TGAHeaderS& rHdr )
{

	m_pData = new uint8[m_i32Width * m_i32Height * 4];
	m_i32Bpp = 32;

	//-- Bypass the image identification field, if present -------------------
	if( rHdr.idlen ) {
		if( fseek( fp,(long)rHdr.idlen, SEEK_CUR )!= 0 )
			return false;
	}

	//-- Bypass the color map, if present ------------------------------------
	if( rHdr.cmlen ) {
		if( fseek( fp, (long)rHdr.cmlen * (long)((rHdr.cmes + 7) / 8), SEEK_CUR ) != 0 )
			return false;
	}

	//-- Read Image File -----------------------------------------------------
	switch( rHdr.imgtype ) {

	//-- Uncompressed RGB -----------------------------
	case 2:
		{
			uint8*	pDest = m_pData;
			for( int32 int32Y = 0; int32Y < m_i32Height; int32Y++ ) {
				if( !fread( pDest, m_i32Width * 4, 1, fp ) )
					return false;

				// swap r & b
				for( int32 i = 0; i < m_i32Width; i++ ) {
					uint8	ui8Tmp = pDest[i * 4];
					pDest[i * 4] = pDest[i * 4 + 2];
					pDest[i * 4 + 2] = ui8Tmp;
					pDest[i * 4 + 3] = pDest[i * 4 + 3];
				}

				pDest += m_i32Width * 4;
			}
		}
		break;
	
	//-- Compressed RGB -------------------------------

	case 10:
		{
			int32	i32X = 0;
			int32	i32Y = 0;
			uint8	ui8Rle;
			uint8	ui8Pixel[4];
			uint8*	pDest = m_pData;
			
			while( 1 ) {
				
				if( !fread( &ui8Rle, 1, 1, fp ) )
					return false;
				
				if( ui8Rle > 127 ) {
					//-- Compressed Block
					ui8Rle -= 127;

					if( !fread( ui8Pixel, 4, 1, fp ) )
						return false;

					for( int32 i = 0; i < ui8Rle; i++ ) {
						pDest[i32X++] = ui8Pixel[2];
						pDest[i32X++] = ui8Pixel[1];
						pDest[i32X++] = ui8Pixel[0];
						pDest[i32X++] = ui8Pixel[3];
						if( i32X >= m_i32Width * 4 ) {
							i32X = 0;
							i32Y++;
							if( i32Y >= m_i32Height )
								break;
							pDest += m_i32Width * 4;
						}
					}
				}
				else {
					//-- Uncompressed Block
					ui8Rle++;

					for( int32 i = 0; i < ui8Rle; i++ ) {

						if( !fread( ui8Pixel, 4, 1, fp ) )
							return false;

						pDest[i32X++] = ui8Pixel[2];
						pDest[i32X++] = ui8Pixel[1];
						pDest[i32X++] = ui8Pixel[0];
						pDest[i32X++] = ui8Pixel[3];

						if( i32X >= m_i32Width * 4 ) {
							i32X = 0;
							i32Y++;
							if( i32Y >= m_i32Height )
								break;
							pDest += m_i32Width * 4;
						}
					}
				}
			}
		}
		break;
	}

	return true;
}


// loads the file
bool
TGAImportC::load_file( const char* szName, DemoInterfaceC* pInterface )
{
	m_pDemoInterface = pInterface;

	FILE*		fp;
	TGAHeaderS	rHeader;

	if( (fp = fopen( pInterface->get_absolute_path( szName ), "rb" )) == 0 ) {
		return false;
	}

	m_sFileName = szName;

	// Return to the beginning of the file
	fseek( fp, 0L, SEEK_SET );

	if( fread( &rHeader, sizeof( TGAHeaderS ), 1, fp ) != 1 )
		return false;

	bool	bHFlip = (rHeader.desc & 0x10) ? true : false;		// Need hflip
	bool	bVFlip = (rHeader.desc & 0x20) ? true : false;		// Need vflip
	rHeader.desc &= 0xcf;										// Mask off flip bits

	m_i32Width = rHeader.width;
	m_i32Height = rHeader.height;

	delete [] m_pData;
	m_pData = 0;

	switch( rHeader.pixsize ) {
	case 8:
		{
			if( rHeader.desc != 0 && rHeader.desc != 1 && rHeader.desc != 8 )
				return false;
			switch( rHeader.imgtype ) {
			case 1:
			case 9:
				load_8bit_pal_TGA( fp, rHeader );
				break;
			case 3:
			case 11:
				load_8bit_gray_TGA( fp, rHeader );
				break;
			}
		}
		break;
	case 16:
		{
			if( rHeader.desc != 0 && rHeader.desc != 1 ) {
//				OutputDebugString( "plaaplaa\n" );
				return false;
			}
			load_16bit_TGA( fp, rHeader );
		}
		break;
	case 24:
		{
			if( rHeader.desc != 0 )
				return false;
			load_24bit_TGA( fp, rHeader );
		}
		break;
	case 32:
		{
			load_32bit_TGA( fp, rHeader );
		}
		break;
	default:
//		OutputDebugString( "invalid bitdepth\n" );
		return false;
	}

//	char	szMsg[256];
//	_snprintf( szMsg, 255, "size: %d x %d x 24bit\n", m_i32Width, m_i32Height );
//	OutputDebugString( szMsg );

	fclose( fp );

	//-- Perform clean-up operations!  
	if( m_pData ) {

		if( bHFlip ) {

//			OutputDebugString( "h-flip\n" );

			if( m_i32Bpp == 8 ) {
				uint8*	pTmpLine = new uint8[m_i32Width];
				uint8*	pDest = m_pData;

				for( int32 i = 0; i < m_i32Height; i++ ) {
					memcpy( pTmpLine, pDest, m_i32Width );
					for( int32 j = 0; j < m_i32Width; j++ ) {
						pDest[j] = pTmpLine[((m_i32Width - 1) - j)];
					}
					pDest += m_i32Width;
				}

				delete pTmpLine;
			}
			else if( m_i32Bpp == 24 ) {
				uint8*	pTmpLine = new uint8[m_i32Width * 3];
				uint8*	pDest = m_pData;

				for( int32 i = 0; i < m_i32Height; i++ ) {
					memcpy( pTmpLine, pDest, m_i32Width * 3 );
					for( int32 j = 0; j < m_i32Width; j++ ) {
						pDest[j * 3] = pTmpLine[((m_i32Width - 1) - j) * 3];
						pDest[j * 3 + 1] = pTmpLine[((m_i32Width - 1) - j) * 3 + 1];
						pDest[j * 3 + 2] = pTmpLine[((m_i32Width - 1) - j) * 3 + 2];
					}
					pDest += m_i32Width * 3;
				}

				delete pTmpLine;
			}
			else if( m_i32Bpp == 32 ) {
				uint8*	pTmpLine = new uint8[m_i32Width * 4];
				uint8*	pDest = m_pData;

				for( int32 i = 0; i < m_i32Height; i++ ) {
					memcpy( pTmpLine, pDest, m_i32Width * 4 );
					for( int32 j = 0; j < m_i32Width; j++ ) {
						pDest[j * 4 + 0] = pTmpLine[((m_i32Width - 1) - j) * 4 + 0];
						pDest[j * 4 + 1] = pTmpLine[((m_i32Width - 1) - j) * 4 + 1];
						pDest[j * 4 + 2] = pTmpLine[((m_i32Width - 1) - j) * 4 + 2];
						pDest[j * 4 + 3] = pTmpLine[((m_i32Width - 1) - j) * 4 + 3];
					}
					pDest += m_i32Width * 4;
				}

				delete pTmpLine;
			}

		}

		if( bVFlip ) {

			if( m_i32Bpp == 8 ) {
				uint8*	pTmpLineTop = new uint8[m_i32Width];
				uint8*	pTmpLineBottom = new uint8[m_i32Width];

				for( uint32 ui32YTop = 0, ui32YBot = m_i32Height - 1; ui32YTop < ui32YBot; ui32YTop++, ui32YBot-- ) {
					memcpy( pTmpLineTop, m_pData + (ui32YTop * m_i32Width), m_i32Width );
					memcpy( pTmpLineBottom, m_pData + (ui32YBot * m_i32Width), m_i32Width );

					memcpy( m_pData + (ui32YTop * m_i32Width), pTmpLineBottom, m_i32Width );
					memcpy( m_pData + (ui32YBot * m_i32Width), pTmpLineTop, m_i32Width );
				}

				delete [] pTmpLineTop;
				delete [] pTmpLineBottom;
			}
			else if( m_i32Bpp == 24 ) {
				uint8*	pTmpLineTop = new uint8[m_i32Width * 3];
				uint8*	pTmpLineBottom = new uint8[m_i32Width * 3];

				for( uint32 ui32YTop = 0, ui32YBot = m_i32Height - 1; ui32YTop < ui32YBot; ui32YTop++, ui32YBot-- ) {
					memcpy( pTmpLineTop, m_pData + (ui32YTop * m_i32Width * 3), m_i32Width * 3 );
					memcpy( pTmpLineBottom, m_pData + (ui32YBot * m_i32Width * 3), m_i32Width * 3 );

					memcpy( m_pData + (ui32YTop * m_i32Width * 3), pTmpLineBottom, m_i32Width * 3 );
					memcpy( m_pData + (ui32YBot * m_i32Width * 3), pTmpLineTop, m_i32Width * 3 );
				}

				delete [] pTmpLineTop;
				delete [] pTmpLineBottom;
			}
			else if( m_i32Bpp == 32 ) {
				uint8*	pTmpLineTop = new uint8[m_i32Width * 4];
				uint8*	pTmpLineBottom = new uint8[m_i32Width * 4];

				for( uint32 ui32YTop = 0, ui32YBot = m_i32Height - 1; ui32YTop < ui32YBot; ui32YTop++, ui32YBot-- ) {
					memcpy( pTmpLineTop, m_pData + (ui32YTop * m_i32Width * 4), m_i32Width * 4 );
					memcpy( pTmpLineBottom, m_pData + (ui32YBot * m_i32Width * 4), m_i32Width * 4 );

					memcpy( m_pData + (ui32YTop * m_i32Width * 4), pTmpLineBottom, m_i32Width * 4 );
					memcpy( m_pData + (ui32YBot * m_i32Width * 4), pTmpLineTop, m_i32Width * 4 );
				}

				delete [] pTmpLineTop;
				delete [] pTmpLineBottom;
			}
		}
	}

	return true;
}

void
TGAImportC::initialize( uint32 ui32Reason, DemoInterfaceC* pInterface )
{
	ImportableI::initialize( ui32Reason, pInterface );

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

	OpenGLDeviceC*	pDevice = (OpenGLDeviceC*)pContext->query_interface( CLASS_OPENGL_DEVICEDRIVER );
	if( !pDevice )
		return;

	if( ui32Reason == INIT_DEVICE_CHANGED ) {

		if( pDevice->get_state() == DEVICE_STATE_SHUTTINGDOWN ) {
			// Delete textures
			if( m_ui32TextureId )
				glDeleteTextures( 1, &m_ui32TextureId );
		}

	}

	if( ui32Reason == INIT_INITIAL_UPDATE ) {
		if( !m_ui32TextureId )
			upload_texture();
	}
}




ClassIdC
TGAImportC::get_class_id()
{
	return CLASS_TGA_IMPORT;
}

const char*
TGAImportC::get_class_name()
{
	return "TGA Image";
}

float32
TGAImportC::get_width()
{
	return (float32)m_i32Width;
}

float32
TGAImportC::get_height()
{
	return (float32)m_i32Height;
}

int32
TGAImportC::get_data_width()
{
	return m_i32Width;
}

int32
TGAImportC::get_data_height()
{
	return m_i32Height;
}

int32
TGAImportC::get_data_pitch()
{
	return m_i32Width;
}

BBox2C&
TGAImportC::get_tex_coord_bounds()
{
	return m_rTexBounds;
}

int32
TGAImportC::get_data_bpp()
{
	return m_i32Bpp;
}

uint8*
TGAImportC::get_data()
{
	return m_pData;
}


void
TGAImportC::upload_texture()
{
	glGenTextures( 1, &m_ui32TextureId );
	glBindTexture( GL_TEXTURE_2D, m_ui32TextureId );
	glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );

	uint8*	pResampleData = 0;

	int32	i32Width = m_i32Width;
	int32	i32Height = m_i32Height;
	uint8*	pData = m_pData;

	int32	i32WidthPow2 = nearest_power2( i32Width );
	int32	i32HeightPow2 = nearest_power2( i32Height );

	if( i32WidthPow2 != m_i32Width || i32HeightPow2 != m_i32Height ) {
		pResampleData = new uint8[i32WidthPow2 * i32HeightPow2 * (m_i32Bpp / 8)];
		ImageResampleC::resample_bilinear( m_i32Bpp, m_pData, m_i32Width, m_i32Height, pResampleData, i32WidthPow2, i32HeightPow2 );
		pData = pResampleData;
		i32Width = i32WidthPow2;
		i32Height = i32HeightPow2;
	}

	if( m_i32Bpp == 8 ) {
		glTexImage2D( GL_TEXTURE_2D, 0, GL_ALPHA8, i32Width, i32Height, 0, GL_ALPHA , GL_UNSIGNED_BYTE, pData );
	}
	else if( m_i32Bpp == 24 ) {
		glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, i32Width, i32Height, 0, GL_RGB, GL_UNSIGNED_BYTE, pData );
	}
	else if( m_i32Bpp ==32 ) {
		glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, i32Width, i32Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pData );
	}

	if( pResampleData )
		delete [] pResampleData;
}

void
TGAImportC::bind_texture( DeviceInterfaceI* pInterface, uint32 ui32Stage, uint32 ui32Properties )
{
	if( pInterface->get_class_id() != CLASS_OPENGL_DEVICEDRIVER )
		return;

	if( !m_ui32TextureId ) {
		upload_texture();
	}
	else
		glBindTexture( GL_TEXTURE_2D, m_ui32TextureId );

	if( ui32Properties & IMAGE_LINEAR ) {
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	}
	else {
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
	}

	if( ui32Properties & IMAGE_CLAMP ) {
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
	}
	else {
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
	}

}

const char*
TGAImportC::get_info()
{
	static char	szInfo[256] = "";
	char*	szType;
	if( m_i32Bpp == 8 )
		szType = "ALPHA";
	else if( m_i32Bpp == 16 )
		szType = "RGB";
	else if( m_i32Bpp == 24 )
		szType = "RGB";
	else if( m_i32Bpp == 32 )
		szType = "RGBA";

	_snprintf( szInfo, 255, "%s %d x %d x %dbit", szType, m_i32Width, m_i32Height, m_i32Bpp );
	return szInfo;
}

ClassIdC
TGAImportC::get_default_effect()
{
	return CLASS_IMAGE_EFFECT;
}


int32
TGAImportC::get_duration()
{
	return -1;
}

float32
TGAImportC::get_start_label()
{
	return 0;
}

float32
TGAImportC::get_end_label()
{
	return 0;
}

void
TGAImportC::eval_state( int32 i32Time )
{
	// empty
}


enum TGAImportChunksE {
	CHUNK_TGAIMPORT_BASE =	0x1000,
	CHUNK_TGAIMPORT_DATA =	0x2000,
};

const uint32	TGAIMPORT_VERSION_1 = 1;
const uint32	TGAIMPORT_VERSION = 2;

uint32
TGAImportC::save( SaveC* pSave )
{
	uint32		ui32Error = IO_OK;
	uint8		ui8Tmp;
	std::string	sStr;

	// file base
	pSave->begin_chunk( CHUNK_TGAIMPORT_BASE, TGAIMPORT_VERSION );
		sStr = m_sFileName;
		if( sStr.size() > 255 )
			sStr.resize( 255 );
		ui32Error = pSave->write_str( sStr.c_str() );
	pSave->end_chunk();

	// file data
	pSave->begin_chunk( CHUNK_TGAIMPORT_DATA, TGAIMPORT_VERSION );
		ui32Error = pSave->write( &m_i32Width, sizeof( m_i32Width ) );
		ui32Error = pSave->write( &m_i32Height, sizeof( m_i32Height ) );
		ui8Tmp = (uint8)m_i32Bpp;
		ui32Error = pSave->write( &ui8Tmp, sizeof( ui8Tmp ) );

		//
		// compress
		//
		int32	i32DataSize = m_i32Width * m_i32Height * (m_i32Bpp / 8);
		uint32	ui32CompressedSize;
		uint8*	pCompressed = new uint8[i32DataSize + i32DataSize / 64 + 16 + 3];
		long*	pWorkmem = new long[((LZO1X_1_MEM_COMPRESS) + (sizeof(long) - 1)) / sizeof(long)];

		if( lzo1x_1_compress( m_pData, i32DataSize, pCompressed, &ui32CompressedSize, pWorkmem ) != LZO_E_OK )
			ui32Error = IO_ERROR_WRITE;

		// write compressed data size
		ui32Error = pSave->write( &ui32CompressedSize, sizeof( ui32CompressedSize ) );

		// write compressed data
		ui32Error = pSave->write( pCompressed, ui32CompressedSize );

		delete [] pWorkmem;
		delete [] pCompressed;

	pSave->end_chunk();

	return ui32Error;
}

uint32
TGAImportC::load( LoadC* pLoad )
{
	uint32	ui32Error = IO_OK;
	char	szStr[256];
	uint8	ui8Tmp;

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

		switch( pLoad->get_chunk_id() ) {
		case CHUNK_TGAIMPORT_BASE:
			{
				if( pLoad->get_chunk_version() <= TGAIMPORT_VERSION ) {
					ui32Error = pLoad->read_str( szStr );
					m_sFileName = szStr;
				}
			}
			break;

		case CHUNK_TGAIMPORT_DATA:
			{
				if( pLoad->get_chunk_version() <= TGAIMPORT_VERSION ) {
					ui32Error = pLoad->read( &m_i32Width, sizeof( m_i32Width ) );
					ui32Error = pLoad->read( &m_i32Height, sizeof( m_i32Height ) );
					ui32Error = pLoad->read( &ui8Tmp, sizeof( ui8Tmp ) );
					m_i32Bpp = ui8Tmp;
					// delete old data if any
					delete m_pData;


					//
					// decompress
					//

					uint32	ui32DataSize = m_i32Width * m_i32Height * (m_i32Bpp / 8);
					uint32	ui32DecompressedSize;
					m_pData = new uint8[ui32DataSize];
					uint32	ui32CompressedSize;

					// read compressed data size
					ui32Error = pLoad->read( &ui32CompressedSize, sizeof( ui32CompressedSize ) );

					uint8*	pCompressed = new uint8[ui32CompressedSize];

					// write compressed data
					ui32Error = pLoad->read( pCompressed, ui32CompressedSize );

					if( lzo1x_decompress( pCompressed, ui32CompressedSize, m_pData, &ui32DecompressedSize, NULL ) != LZO_E_OK )
						ui32Error = IO_ERROR_READ;

					if( ui32DecompressedSize != ui32DataSize )
						OutputDebugString( "data size mismatch\n" );

					delete [] pCompressed;

					// The version 1 had the image updside down.
					if( pLoad->get_chunk_version() == TGAIMPORT_VERSION_1 ) {

						uint32	ui32RowSize = m_i32Width * (m_i32Bpp / 8);

						uint8*	pTmpLineTop = new uint8[ui32RowSize];
						uint8*	pTmpLineBottom = new uint8[ui32RowSize];

						for( uint32 ui32YTop = 0, ui32YBot = m_i32Height - 1; ui32YTop < ui32YBot; ui32YTop++, ui32YBot-- ) {
							memcpy( pTmpLineTop, m_pData + (ui32YTop * ui32RowSize), ui32RowSize );
							memcpy( pTmpLineBottom, m_pData + (ui32YBot * ui32RowSize), ui32RowSize );

							memcpy( m_pData + (ui32YTop * ui32RowSize), pTmpLineBottom, ui32RowSize );
							memcpy( m_pData + (ui32YBot * ui32RowSize), pTmpLineTop, ui32RowSize );
						}

						delete [] pTmpLineTop;
						delete [] pTmpLineBottom;
					}
				}
			}
			break;

		default:
			assert( 0 );
		}

		pLoad->close_chunk();

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

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

	return IO_OK;
}


/*
// Insert your headers here
#define WIN32_LEAN_AND_MEAN		// Exclude rarely-used stuff from Windows headers
#include <windows.h>
#include <gl\gl.h>
#include <gl\glu.h>
#include "glext.h"
#include <stdio.h>
#include <string>
#include "ImagePlugin.h"

#include "DemopajaVersion.h"
#include "PajaTypes.h"
#include "ClassDescC.h"
#include "ParamI.h"
#include "EditableI.h"
#include "UndoC.h"
#include "ImportableImageI.h"
#include "BBox2C.h"
#include "Matrix2C.h"
#include "DeviceContextC.h"
#include "DeviceInterfaceI.h"
#include "DemoInterfaceC.h"
#include "OpenGLDeviceC.h"
#include "OpenGLViewportC.h"
#include "TimeContextC.h"
#include "ImageResampleC.h"

#include "minilzo.h"


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

// class descriptors
TestDescC		g_rTestDesc;
TGAImportDescC	g_rTGAImportDesc;


#ifndef PAJAPLAYER

//
// The DLL
//

BOOL APIENTRY
DllMain( HANDLE hModule, DWORD ulReasonForCall, LPVOID lpReserved )
{
    switch( ulReasonForCall )
	{
		case DLL_PROCESS_ATTACH:
		case DLL_THREAD_ATTACH:
		case DLL_THREAD_DETACH:
		case DLL_PROCESS_DETACH:
			break;
    }
    return TRUE;
}



__declspec( dllexport )
int32
get_classdesc_count()
{
	return 2;
}

__declspec( dllexport )
ClassDescC*
get_classdesc( int32 i )
{
	if( i == 0 )
		return &g_rTestDesc;
	else if( i == 1 )
		return &g_rTGAImportDesc;
	return 0;
}

__declspec( dllexport )
int32
get_api_version()
{
	return DEMOPAJA_VERSION;
}

__declspec( dllexport )
char*
get_dll_name()
{
	return "testplugin.dll - Testi plugini (c)2000 memon/moppi productions";
}

#endif

//--------------------------------------------------------------------------------------------------------------
//
// old test gizmo
//
//--------------------------------------------------------------------------------------------------------------

TestGizmoC::TestGizmoC()
{
	init();
}

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

TestGizmoC::TestGizmoC( EditableI* pOriginal ) :
	GizmoI( pOriginal )
{
	// empty
}

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

	m_pParamPos = ParamVector2C::create_new( this, "Position", Vector2C(), 1, PARAM_STYLE_EDITBOX | PARAM_STYLE_ABS_POSITION, PARAM_ANIMATABLE );
	
	m_pParamPivot = ParamVector2C::create_new( this, "Pivot", Vector2C(), 2, PARAM_STYLE_EDITBOX | PARAM_STYLE_REL_POSITION | PARAM_STYLE_WORLD_SPACE	, PARAM_ANIMATABLE );
	
	m_pParamRot = ParamFloatC::create_new( this, "Rotation", 0, 3, PARAM_STYLE_EDITBOX | PARAM_STYLE_ANGLE, PARAM_ANIMATABLE, 0, 0, 1.0f );
	
	m_pParamScale = ParamVector2C::create_new( this, "Scale", Vector2C( 1, 1 ), 4, PARAM_STYLE_EDITBOX | PARAM_STYLE_PERCENT, PARAM_ANIMATABLE, Vector2C(), Vector2C(), 0.01f );
	
	m_pParamColor = ParamColorC::create_new( this, "Color", ColorC( 1, 1, 1, 1 ), 5, PARAM_STYLE_COLORPICKER_RGBA, PARAM_ANIMATABLE );

	m_pParamRenderMode = ParamIntC::create_new( this, "Render mode", 0, 6, PARAM_STYLE_COMBOBOX, PARAM_ANIMATABLE, 0, 2 );
	m_pParamRenderMode->add_label( 0, "Normal" );
	m_pParamRenderMode->add_label( 1, "Add" );
	m_pParamRenderMode->add_label( 2, "Mult" );

	m_pParamFilterMode = ParamIntC::create_new( this, "Filter mode", 0, 7, PARAM_STYLE_COMBOBOX, PARAM_ANIMATABLE, 0, 1 );
	m_pParamFilterMode->add_label( 0, "Bilinear" );
	m_pParamFilterMode->add_label( 1, "Nearest" );

	m_pParamShape = ParamIntC::create_new( this, "Shape", 0, 8, PARAM_STYLE_COMBOBOX, PARAM_ANIMATABLE, 0, 1 );
	m_pParamShape->add_label( 0, "Rectangle" );
	m_pParamShape->add_label( 1, "Oval" );

	m_pParamFile = ParamFileC::create_new( this, "Image", SUPERCLASS_IMAGE, NULL_CLASSID, 9, PARAM_ANIMATABLE );

}

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

	m_pParamPos->release();
	m_pParamPivot->release();
	m_pParamRot->release();
	m_pParamScale->release();
	m_pParamColor->release();
	m_pParamFile->release();
	m_pParamRenderMode->release();
	m_pParamFilterMode->release();
	m_pParamShape->release();
}


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

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

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

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

	TestGizmoC*	pGizmo = (TestGizmoC*)pEditable;
	m_pParamPos->copy( pGizmo->m_pParamPos );
	m_pParamPivot->copy( pGizmo->m_pParamPivot );
	m_pParamRot->copy( pGizmo->m_pParamRot );
	m_pParamScale->copy( pGizmo->m_pParamScale );
	m_pParamColor->copy( pGizmo->m_pParamColor );
	m_pParamFile->copy( pGizmo->m_pParamFile );
	m_pParamRenderMode->copy( pGizmo->m_pParamRenderMode );
	m_pParamFilterMode->copy( pGizmo->m_pParamFilterMode );
	m_pParamShape->copy( pGizmo->m_pParamShape );
}

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

	TestGizmoC*	pGizmo = (TestGizmoC*)pEditable;
	m_pParamPos =  pGizmo->m_pParamPos;
	m_pParamPivot =  pGizmo->m_pParamPivot;
	m_pParamRot =  pGizmo->m_pParamRot;
	m_pParamScale =  pGizmo->m_pParamScale;
	m_pParamColor =  pGizmo->m_pParamColor;
	m_pParamFile =  pGizmo->m_pParamFile;
	m_pParamRenderMode = pGizmo->m_pParamRenderMode;
	m_pParamFilterMode = pGizmo->m_pParamFilterMode;
	m_pParamShape = pGizmo->m_pParamShape;
}


PajaTypes::int32
TestGizmoC::get_parameter_count()
{
	return TEST_PARAM_COUNT;
}

ParamI*
TestGizmoC::get_parameter( PajaTypes::int32 i32Index )
{
	switch( i32Index ) {
	case TEST_PARAM_POS:
		return m_pParamPos; break;
	case TEST_PARAM_PIVOT:
		return m_pParamPivot; break;
	case TEST_PARAM_ROT:
		return m_pParamRot; break;
	case TEST_PARAM_SCALE:
		return m_pParamScale; break;
	case TEST_PARAM_COLOR:
		return m_pParamColor; break;
	case TEST_PARAM_RENDERMODE:
		return m_pParamRenderMode; break;
	case TEST_PARAM_FILTERMODE:
		return m_pParamFilterMode; break;
	case TEST_PARAM_SHAPE:
		return m_pParamShape; break;
	case TEST_PARAM_FILE:
		return m_pParamFile; break;
	}
	return 0;
}


int32
TestGizmoC::get_shape( int32 i32Time )
{
	int32	i32Val;
	m_pParamShape->get_val( i32Time, i32Val );
	return i32Val;
}

ImportableImageI*
TestGizmoC::get_file( int32 i32Time, DeviceContextC* pContext, TimeContextC* pTimeContext )
{
	FileHandleC*	pHandle = 0;
	int32			i32FileTime = 0;
	m_pParamFile->get_file( i32Time, pHandle, i32FileTime );
	if( pHandle ) {
		ImportableImageI*	pImp = (ImportableImageI*)pHandle->get_importable();
		pImp->eval_state( i32FileTime );
		return pImp;
	}
	return 0;
}


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

Vector2C
TestGizmoC::get_pivot( int32 i32Time )
{
	Vector2C	rVal;
	m_pParamPivot->get_val( i32Time, rVal );
	return rVal;
}

float32
TestGizmoC::get_rot( int32 i32Time )
{
	float32	f32Val;
	m_pParamRot->get_val( i32Time, f32Val );
	return f32Val;
}

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

ColorC
TestGizmoC::get_color( int32 i32Time )
{
	ColorC	rVal;
	m_pParamColor->get_val( i32Time, rVal );
	return rVal;
}

int32
TestGizmoC::get_render_mode( PajaTypes::int32 i32Time )
{
	int32	i32Val;
	m_pParamRenderMode->get_val( i32Time, i32Val );
	return i32Val;
}

int32
TestGizmoC::get_filter_mode( PajaTypes::int32 i32Time )
{
	int32	i32Val;
	m_pParamFilterMode->get_val( i32Time, i32Val );
	return i32Val;
}


enum TestGizmoChunksE {
	CHUNK_TESTGIZMO_BASE				= 0x1000,
	CHUNK_TESTGIZMO_PARAM_POS			= 0x2000,
	CHUNK_TESTGIZMO_PARAM_PIVOT			= 0x3000,
	CHUNK_TESTGIZMO_PARAM_ROT			= 0x4000,
	CHUNK_TESTGIZMO_PARAM_SCALE			= 0x5000,
	CHUNK_TESTGIZMO_PARAM_COLOR			= 0x6000,
	CHUNK_TESTGIZMO_PARAM_RENDERMODE	= 0x7000,
	CHUNK_TESTGIZMO_PARAM_FILE			= 0x8000,
	CHUNK_TESTGIZMO_PARAM_MASKFILE		= 0x9000,
	CHUNK_TESTGIZMO_PARAM_FILTERMODE	= 0xA000,
	CHUNK_TESTGIZMO_PARAM_SHAPE			= 0xB000,
};

const uint32	TESTGIZMO_VERSION = 1;



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

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

	// position
	pSave->begin_chunk( CHUNK_TESTGIZMO_PARAM_POS, TESTGIZMO_VERSION );
		ui32Error = m_pParamPos->save( pSave );
	pSave->end_chunk();

	// position
	pSave->begin_chunk( CHUNK_TESTGIZMO_PARAM_PIVOT, TESTGIZMO_VERSION );
		ui32Error = m_pParamPivot->save( pSave );
	pSave->end_chunk();

	// rotation
	pSave->begin_chunk( CHUNK_TESTGIZMO_PARAM_ROT, TESTGIZMO_VERSION );
		ui32Error = m_pParamRot->save( pSave );
	pSave->end_chunk();

	// scale
	pSave->begin_chunk( CHUNK_TESTGIZMO_PARAM_SCALE, TESTGIZMO_VERSION );
		ui32Error = m_pParamScale->save( pSave );
	pSave->end_chunk();

	// color
	pSave->begin_chunk( CHUNK_TESTGIZMO_PARAM_COLOR, TESTGIZMO_VERSION );
		ui32Error = m_pParamColor->save( pSave );
	pSave->end_chunk();

	// render
	pSave->begin_chunk( CHUNK_TESTGIZMO_PARAM_RENDERMODE, TESTGIZMO_VERSION );
		ui32Error = m_pParamRenderMode->save( pSave );
	pSave->end_chunk();

	// filter
	pSave->begin_chunk( CHUNK_TESTGIZMO_PARAM_FILTERMODE, TESTGIZMO_VERSION );
		ui32Error = m_pParamFilterMode->save( pSave );
	pSave->end_chunk();

	// shape
	pSave->begin_chunk( CHUNK_TESTGIZMO_PARAM_SHAPE, TESTGIZMO_VERSION );
		ui32Error = m_pParamShape->save( pSave );
	pSave->end_chunk();

	// file
	pSave->begin_chunk( CHUNK_TESTGIZMO_PARAM_FILE, TESTGIZMO_VERSION );
		ui32Error = m_pParamFile->save( pSave );
	pSave->end_chunk();

	return ui32Error;
}

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

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

		switch( pLoad->get_chunk_id() ) {
		case CHUNK_TESTGIZMO_BASE:
			{
				if( pLoad->get_chunk_version() == TESTGIZMO_VERSION )
					ui32Error = GizmoI::load( pLoad );
			}
			break;

		case CHUNK_TESTGIZMO_PARAM_POS:
			{
				if( pLoad->get_chunk_version() == TESTGIZMO_VERSION )
					ui32Error = m_pParamPos->load( pLoad );
			}
			break;

		case CHUNK_TESTGIZMO_PARAM_PIVOT:
			{
				if( pLoad->get_chunk_version() == TESTGIZMO_VERSION )
					ui32Error = m_pParamPivot->load( pLoad );
			}
			break;

		case CHUNK_TESTGIZMO_PARAM_ROT:
			{
				if( pLoad->get_chunk_version() == TESTGIZMO_VERSION )
					ui32Error = m_pParamRot->load( pLoad );
			}
			break;

		case CHUNK_TESTGIZMO_PARAM_SCALE:
			{
				if( pLoad->get_chunk_version() == TESTGIZMO_VERSION )
					ui32Error = m_pParamScale->load( pLoad );
			}
			break;

		case CHUNK_TESTGIZMO_PARAM_COLOR:
			{
				if( pLoad->get_chunk_version() == TESTGIZMO_VERSION )
					ui32Error = m_pParamColor->load( pLoad );
			}
			break;

		case CHUNK_TESTGIZMO_PARAM_RENDERMODE:
			{
				if( pLoad->get_chunk_version() == TESTGIZMO_VERSION )
					ui32Error = m_pParamRenderMode->load( pLoad );
			}
			break;

		case CHUNK_TESTGIZMO_PARAM_FILTERMODE:
			{
				if( pLoad->get_chunk_version() == TESTGIZMO_VERSION )
					ui32Error = m_pParamFilterMode->load( pLoad );
			}
			break;

		case CHUNK_TESTGIZMO_PARAM_SHAPE:
			{
				if( pLoad->get_chunk_version() == TESTGIZMO_VERSION )
					ui32Error = m_pParamShape->load( pLoad );
			}
			break;

		case CHUNK_TESTGIZMO_PARAM_FILE:
			{
				if( pLoad->get_chunk_version() == TESTGIZMO_VERSION )
					ui32Error = m_pParamFile->load( pLoad );
			}
			break;

		case CHUNK_TESTGIZMO_PARAM_MASKFILE:
			{
				// just for backward compability... obsolete
			}
			break;

		default:
			assert( 0 );
		}

		pLoad->close_chunk();

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

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

	return IO_OK;
}


//--------------------------------------------------------------------------------------------------------------
//
// Transform gizmo
//
//--------------------------------------------------------------------------------------------------------------

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

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

TransformGizmoC::TransformGizmoC( EditableI* pOriginal ) :
	GizmoI( pOriginal )
{
//	init();
}

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

	m_pParamPos = ParamVector2C::create_new( this, "Position", Vector2C(), 1, PARAM_STYLE_EDITBOX | PARAM_STYLE_ABS_POSITION, PARAM_ANIMATABLE );
	m_pParamPivot = ParamVector2C::create_new( this, "Pivot", Vector2C(), 2, PARAM_STYLE_EDITBOX | PARAM_STYLE_REL_POSITION | PARAM_STYLE_WORLD_SPACE, PARAM_ANIMATABLE );
	m_pParamRot = ParamFloatC::create_new( this, "Rotation", 0, 3, PARAM_STYLE_EDITBOX | PARAM_STYLE_ANGLE, PARAM_ANIMATABLE, 0, 0, 1.0f );
	m_pParamScale = ParamVector2C::create_new( this, "Scale", Vector2C( 1, 1 ), 4, PARAM_STYLE_EDITBOX | PARAM_STYLE_PERCENT, PARAM_ANIMATABLE, Vector2C(), Vector2C(), 0.01f );
}

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

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


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 )
{
	GizmoI::copy( pEditable );

	TransformGizmoC*	pGizmo = (TransformGizmoC*)pEditable;
	m_pParamPos->copy( pGizmo->m_pParamPos );
	m_pParamPivot->copy( pGizmo->m_pParamPivot );
	m_pParamRot->copy( pGizmo->m_pParamRot );
	m_pParamScale->copy( pGizmo->m_pParamScale );
}

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

	TransformGizmoC*	pGizmo = (TransformGizmoC*)pEditable;
	m_pParamPos =  pGizmo->m_pParamPos;
	m_pParamPivot =  pGizmo->m_pParamPivot;
	m_pParamRot =  pGizmo->m_pParamRot;
	m_pParamScale =  pGizmo->m_pParamScale;
}


PajaTypes::int32
TransformGizmoC::get_parameter_count()
{
	return TRANSFORM_PARAM_COUNT;
}

ParamI*
TransformGizmoC::get_parameter( PajaTypes::int32 i32Index )
{
	switch( i32Index ) {
	case TRANSFORM_PARAM_POS:
		return m_pParamPos; break;
	case TRANSFORM_PARAM_PIVOT:
		return m_pParamPivot; break;
	case TRANSFORM_PARAM_ROT:
		return m_pParamRot; break;
	case TRANSFORM_PARAM_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_pivot( int32 i32Time )
{
	Vector2C	rVal;
	m_pParamPivot->get_val( i32Time, rVal );
	return rVal;
}

float32
TransformGizmoC::get_rot( int32 i32Time )
{
	float32	f32Val;
	m_pParamRot->get_val( i32Time, f32Val );
	return f32Val;
}

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_PIVOT		= 0x3000,
	CHUNK_TRANSFORMGIZMO_PARAM_ROT			= 0x4000,
	CHUNK_TRANSFORMGIZMO_PARAM_SCALE		= 0x5000,
};

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();

	// pivot
	pSave->begin_chunk( CHUNK_TRANSFORMGIZMO_PARAM_PIVOT, TRANSFORMGIZMO_VERSION );
		ui32Error = m_pParamPivot->save( pSave );
	pSave->end_chunk();

	// rotation
	pSave->begin_chunk( CHUNK_TRANSFORMGIZMO_PARAM_ROT, TRANSFORMGIZMO_VERSION );
		ui32Error = m_pParamRot->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_PIVOT:
			{
				if( pLoad->get_chunk_version() == TRANSFORMGIZMO_VERSION )
					ui32Error = m_pParamPivot->load( pLoad );
			}
			break;

		case CHUNK_TRANSFORMGIZMO_PARAM_ROT:
			{
				if( pLoad->get_chunk_version() == TRANSFORMGIZMO_VERSION )
					ui32Error = m_pParamRot->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;
	}

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

	return IO_OK;
}


//--------------------------------------------------------------------------------------------------------------
//
// attribute gizmo
//
//--------------------------------------------------------------------------------------------------------------

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

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

AttributeGizmoC::AttributeGizmoC( EditableI* pOriginal ) :
	GizmoI( pOriginal )
{
//	init();
}

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

	m_pParamFillColor = ParamColorC::create_new( this, "Fill Color", ColorC( 1, 1, 1, 1 ), 5, PARAM_STYLE_COLORPICKER_RGBA, PARAM_ANIMATABLE );
	m_pParamFillFile = ParamFileC::create_new( this, "Fill Image", SUPERCLASS_IMAGE, NULL_CLASSID, 9, PARAM_STYLE_EDITBOX, PARAM_ANIMATABLE );

	m_pParamOutlineColor = ParamColorC::create_new( this, "Outline Color", ColorC( 1, 1, 1, 1 ), 5, PARAM_STYLE_COLORPICKER_RGBA, PARAM_ANIMATABLE );
	m_pParamOutlineWidth = ParamFloatC::create_new( this, "Outline Width", 0, 3, PARAM_STYLE_EDITBOX, PARAM_ANIMATABLE, 0, 0, 0.1f );

	m_pParamRenderMode = ParamIntC::create_new( this, "Render mode", 0, 6, PARAM_STYLE_COMBOBOX, PARAM_ANIMATABLE, 0, 2 );
	m_pParamRenderMode->add_label( 0, "Normal" );
	m_pParamRenderMode->add_label( 1, "Add" );
	m_pParamRenderMode->add_label( 2, "Mult" );

	m_pParamFilterMode = ParamIntC::create_new( this, "Filter mode", 0, 7, PARAM_STYLE_COMBOBOX, PARAM_ANIMATABLE, 0, 1 );
	m_pParamFilterMode->add_label( 0, "Bilinear" );
	m_pParamFilterMode->add_label( 1, "Nearest" );

	m_pParamShape = ParamIntC::create_new( this, "Shape", 0, 8, PARAM_STYLE_COMBOBOX, PARAM_ANIMATABLE, 0, 1 );
	m_pParamShape->add_label( 0, "Rectangle" );
	m_pParamShape->add_label( 1, "Oval" );
}

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

	m_pParamFillColor->release();
	m_pParamFillFile->release();
	m_pParamRenderMode->release();
	m_pParamFilterMode->release();
	m_pParamShape->release();
	m_pParamOutlineColor->release();
	m_pParamOutlineWidth->release();
}


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_pParamFillColor->copy( pGizmo->m_pParamFillColor );
	m_pParamFillFile->copy( pGizmo->m_pParamFillFile );
	m_pParamOutlineColor->copy( pGizmo->m_pParamOutlineColor );
	m_pParamOutlineWidth->copy( pGizmo->m_pParamOutlineWidth );
	m_pParamRenderMode->copy( pGizmo->m_pParamRenderMode );
	m_pParamFilterMode->copy( pGizmo->m_pParamFilterMode );
	m_pParamShape->copy( pGizmo->m_pParamShape );
}

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

	AttributeGizmoC*	pGizmo = (AttributeGizmoC*)pEditable;
	m_pParamOutlineColor = pGizmo->m_pParamOutlineColor;
	m_pParamOutlineWidth = pGizmo->m_pParamOutlineWidth;
	m_pParamFillColor = pGizmo->m_pParamFillColor;
	m_pParamFillFile = pGizmo->m_pParamFillFile;
	m_pParamRenderMode = pGizmo->m_pParamRenderMode;
	m_pParamFilterMode = pGizmo->m_pParamFilterMode;
	m_pParamShape = pGizmo->m_pParamShape;
}


PajaTypes::int32
AttributeGizmoC::get_parameter_count()
{
	return ATTRIBUTE_PARAM_COUNT;
}

ParamI*
AttributeGizmoC::get_parameter( PajaTypes::int32 i32Index )
{
	switch( i32Index ) {
	case ATTRIBUTE_PARAM_OUTLINE_COLOR:
		return m_pParamOutlineColor; break;
	case ATTRIBUTE_PARAM_OUTLINE_WIDTH:
		return m_pParamOutlineWidth; break;
	case ATTRIBUTE_PARAM_RENDERMODE:
		return m_pParamRenderMode; break;
	case ATTRIBUTE_PARAM_FILTERMODE:
		return m_pParamFilterMode; break;
	case ATTRIBUTE_PARAM_SHAPE:
		return m_pParamShape; break;
	case ATTRIBUTE_PARAM_FILL_COLOR:
		return m_pParamFillColor; break;
	case ATTRIBUTE_PARAM_FILL_FILE:
		return m_pParamFillFile; break;
	}
	return 0;
}


int32
AttributeGizmoC::get_shape( int32 i32Time )
{
	int32	i32Val;
	m_pParamShape->get_val( i32Time, i32Val );
	return i32Val;
}

ImportableImageI*
AttributeGizmoC::get_fill_file( int32 i32Time )
{
	FileHandleC*	pHandle = 0;
	int32			i32FileTime = 0;
	m_pParamFillFile->get_file( i32Time, pHandle, i32FileTime );
	if( pHandle ) {
		ImportableImageI*	pImp = (ImportableImageI*)pHandle->get_importable();
		pImp->eval_state( i32FileTime );
		return pImp;
	}
	return 0;
}


float32
AttributeGizmoC::get_outline_width( int32 i32Time )
{
	float32	f32Val;
	m_pParamOutlineWidth->get_val( i32Time, f32Val );
	return f32Val;
}

ColorC
AttributeGizmoC::get_outline_color( int32 i32Time )
{
	ColorC	rVal;
	m_pParamOutlineColor->get_val( i32Time, rVal );
	return rVal;
}

ColorC
AttributeGizmoC::get_fill_color( int32 i32Time )
{
	ColorC	rVal;
	m_pParamFillColor->get_val( i32Time, rVal );
	return rVal;
}

int32
AttributeGizmoC::get_render_mode( PajaTypes::int32 i32Time )
{
	int32	i32Val;
	m_pParamRenderMode->get_val( i32Time, i32Val );
	return i32Val;
}

int32
AttributeGizmoC::get_filter_mode( PajaTypes::int32 i32Time )
{
	int32	i32Val;
	m_pParamFilterMode->get_val( i32Time, i32Val );
	return i32Val;
}

enum AttributeGizmoChunksE {
	CHUNK_ATTRIBUTEGIZMO_BASE					= 0x1000,
	CHUNK_ATTRIBUTEGIZMO_PARAM_SHAPE			= 0x2000,
	CHUNK_ATTRIBUTEGIZMO_PARAM_RENDERMODE		= 0x3000,
	CHUNK_ATTRIBUTEGIZMO_PARAM_FILTERMODE		= 0x4000,
	CHUNK_ATTRIBUTEGIZMO_PARAM_OUTLINE_WIDTH	= 0x5000,
	CHUNK_ATTRIBUTEGIZMO_PARAM_OUTLINE_COLOR	= 0x6000,
	CHUNK_ATTRIBUTEGIZMO_PARAM_FILL_COLOR		= 0x7000,
	CHUNK_ATTRIBUTEGIZMO_PARAM_FILL_FILE		= 0x8000,
};

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();

	// shape
	pSave->begin_chunk( CHUNK_ATTRIBUTEGIZMO_PARAM_SHAPE, ATTRIBUTEGIZMO_VERSION );
		ui32Error = m_pParamShape->save( pSave );
	pSave->end_chunk();

	// render
	pSave->begin_chunk( CHUNK_ATTRIBUTEGIZMO_PARAM_RENDERMODE, ATTRIBUTEGIZMO_VERSION );
		ui32Error = m_pParamRenderMode->save( pSave );
	pSave->end_chunk();

	// filter
	pSave->begin_chunk( CHUNK_ATTRIBUTEGIZMO_PARAM_FILTERMODE, ATTRIBUTEGIZMO_VERSION );
		ui32Error = m_pParamFilterMode->save( pSave );
	pSave->end_chunk();

	// outline width
	pSave->begin_chunk( CHUNK_ATTRIBUTEGIZMO_PARAM_OUTLINE_WIDTH, ATTRIBUTEGIZMO_VERSION );
		ui32Error = m_pParamOutlineWidth->save( pSave );
	pSave->end_chunk();

	// outline color
	pSave->begin_chunk( CHUNK_ATTRIBUTEGIZMO_PARAM_OUTLINE_COLOR, ATTRIBUTEGIZMO_VERSION );
		ui32Error = m_pParamOutlineColor->save( pSave );
	pSave->end_chunk();

	// fill color
	pSave->begin_chunk( CHUNK_ATTRIBUTEGIZMO_PARAM_FILL_COLOR, ATTRIBUTEGIZMO_VERSION );
		ui32Error = m_pParamFillColor->save( pSave );
	pSave->end_chunk();

	// fill file
	pSave->begin_chunk( CHUNK_ATTRIBUTEGIZMO_PARAM_FILL_FILE, ATTRIBUTEGIZMO_VERSION );
		ui32Error = m_pParamFillFile->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_SHAPE:
			{
				if( pLoad->get_chunk_version() == ATTRIBUTEGIZMO_VERSION )
					ui32Error = m_pParamShape->load( pLoad );
			}
			break;

		case CHUNK_ATTRIBUTEGIZMO_PARAM_RENDERMODE:
			{
				if( pLoad->get_chunk_version() == ATTRIBUTEGIZMO_VERSION )
					ui32Error = m_pParamRenderMode->load( pLoad );
			}
			break;

		case CHUNK_ATTRIBUTEGIZMO_PARAM_FILTERMODE:
			{
				if( pLoad->get_chunk_version() == ATTRIBUTEGIZMO_VERSION )
					ui32Error = m_pParamFilterMode->load( pLoad );
			}
			break;

		case CHUNK_ATTRIBUTEGIZMO_PARAM_OUTLINE_COLOR:
			{
				if( pLoad->get_chunk_version() == ATTRIBUTEGIZMO_VERSION )
					ui32Error = m_pParamOutlineColor->load( pLoad );
			}
			break;

		case CHUNK_ATTRIBUTEGIZMO_PARAM_OUTLINE_WIDTH:
			{
				if( pLoad->get_chunk_version() == ATTRIBUTEGIZMO_VERSION )
					ui32Error = m_pParamOutlineWidth->load( pLoad );
			}
			break;

		case CHUNK_ATTRIBUTEGIZMO_PARAM_FILL_COLOR:
			{
				if( pLoad->get_chunk_version() == ATTRIBUTEGIZMO_VERSION )
					ui32Error = m_pParamFillColor->load( pLoad );
			}
			break;

		case CHUNK_ATTRIBUTEGIZMO_PARAM_FILL_FILE:
			{
				if( pLoad->get_chunk_version() == ATTRIBUTEGIZMO_VERSION )
					ui32Error = m_pParamFillFile->load( pLoad );
			}
			break;

		default:
			assert( 0 );
		}

		pLoad->close_chunk();

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

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

	return IO_OK;
}




//--------------------------------------------------------------------------------------------------------------
//
// The effect
//
//--------------------------------------------------------------------------------------------------------------

TestEffectC::TestEffectC() :
	m_pTestGizmo( 0 )
{
	m_pTransGizmo = TransformGizmoC::create_new( this, 0 );
	m_pAttribGizmo = AttributeGizmoC::create_new( this, 1 );
}

TestEffectC::TestEffectC( EditableI* pOriginal ) :
	EffectI( pOriginal ),
	m_pTestGizmo( 0 ),
	m_pTransGizmo( 0 ),
	m_pAttribGizmo( 0 )
{
//	m_pGizmo = TestGizmoC::create_new();
//	m_pGizmo = 0;
}

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

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

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

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

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

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

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

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

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

const char*
TestEffectC::get_class_name()
{
	return "Image";
}

int32
TestEffectC::get_gizmo_count()
{
	return 2;
}

GizmoI*
TestEffectC::get_gizmo( PajaTypes::int32 i32Index )
{
	if( i32Index == 0 )
		return m_pTransGizmo;
	else if( i32Index == 1 )
		return m_pAttribGizmo;

	return 0;
}

ClassIdC
TestEffectC::get_class_id()
{
	return CLASS_IMAGE_EFFECT;
}

void
TestEffectC::set_default_file( int32 i32Time, FileHandleC* pHandle )
{
	ParamFileC*	pParam = (ParamFileC*)m_pAttribGizmo->get_parameter( ATTRIBUTE_PARAM_FILL_FILE );

	UndoC*	pOldUndo;
	if( get_undo() ) {
		pOldUndo = pParam->begin_editing( get_undo() );
	}

	pParam->set_file( i32Time, pHandle );

	if( get_undo() ) {
		pParam->end_editing( pOldUndo );
	}
}

ParamI*
TestEffectC::get_default_param( PajaTypes::int32 i32Param )
{
	if( i32Param == DEFAULT_PARAM_POSITION )
		return m_pTransGizmo->get_parameter( TRANSFORM_PARAM_POS );
	else if( i32Param == DEFAULT_PARAM_ROTATION )
		return m_pTransGizmo->get_parameter( TRANSFORM_PARAM_ROT );
	else if( i32Param == DEFAULT_PARAM_SCALE )
		return m_pTransGizmo->get_parameter( TRANSFORM_PARAM_SCALE );
	else if( i32Param == DEFAULT_PARAM_PIVOT )
		return m_pTransGizmo->get_parameter( TRANSFORM_PARAM_PIVOT );
	return 0;
}


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

void
TestEffectC::initialize( uint32 ui32Reason, PajaSystem::DemoInterfaceC* pInterface )
{
	EffectI::initialize( ui32Reason, pInterface );

	if( ui32Reason == INIT_INITIAL_UPDATE ) {
		if( m_pTestGizmo ) {

			//
			// we have the old gizmo loaded.
			// convert it to the new structure and delete it
			// the parameters belongs to the testgizmo so we must change the parents too!

			ParamI*	pParam;

			// copy transform
			pParam = m_pTransGizmo->get_parameter( TRANSFORM_PARAM_POS );
			pParam->copy( m_pTestGizmo->get_parameter( TEST_PARAM_POS ) );
			pParam->set_parent( m_pTransGizmo );

			pParam = m_pTransGizmo->get_parameter( TRANSFORM_PARAM_PIVOT );
			pParam->copy( m_pTestGizmo->get_parameter( TEST_PARAM_PIVOT ) );
			pParam->set_parent( m_pTransGizmo );

			pParam = m_pTransGizmo->get_parameter( TRANSFORM_PARAM_ROT );
			pParam->copy( m_pTestGizmo->get_parameter( TEST_PARAM_ROT ) );
			pParam->set_parent( m_pTransGizmo );

			pParam = m_pTransGizmo->get_parameter( TRANSFORM_PARAM_SCALE );
			pParam->copy( m_pTestGizmo->get_parameter( TEST_PARAM_SCALE ) );
			pParam->set_parent( m_pTransGizmo );

			// copy attributes
			pParam = m_pAttribGizmo->get_parameter( ATTRIBUTE_PARAM_FILL_COLOR );
			pParam->copy( m_pTestGizmo->get_parameter( TEST_PARAM_COLOR ) );
			pParam->set_parent( m_pAttribGizmo );

			pParam = m_pAttribGizmo->get_parameter( ATTRIBUTE_PARAM_FILL_FILE );
			pParam->copy( m_pTestGizmo->get_parameter( TEST_PARAM_FILE ) );
			pParam->set_parent( m_pAttribGizmo );

			pParam = m_pAttribGizmo->get_parameter( ATTRIBUTE_PARAM_RENDERMODE );
			pParam->copy( m_pTestGizmo->get_parameter( TEST_PARAM_RENDERMODE ) );
			pParam->set_parent( m_pAttribGizmo );
			
			pParam = m_pAttribGizmo->get_parameter( ATTRIBUTE_PARAM_FILTERMODE );
			pParam->copy( m_pTestGizmo->get_parameter( TEST_PARAM_FILTERMODE ) );
			pParam->set_parent( m_pAttribGizmo );
			
			pParam = m_pAttribGizmo->get_parameter( ATTRIBUTE_PARAM_SHAPE );
			pParam->copy( m_pTestGizmo->get_parameter( TEST_PARAM_SHAPE ) );
			pParam->set_parent( m_pAttribGizmo );

			m_pTestGizmo->release();
			m_pTestGizmo = 0;
		}
	}
}

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

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

	Matrix2C	rPosMat, rRotMat, rScaleMat, rPivotMat;

	Vector2C	rScale = m_pTransGizmo->get_scale( i32Time );
	Vector2C	rPos = m_pTransGizmo->get_pos( i32Time );
	Vector2C	rPivot = m_pTransGizmo->get_pivot( i32Time );
	float32		f32Rot = m_pTransGizmo->get_rot( i32Time );


	m_f32Angle = f32Rot / 180.0f * (float32)M_PI;

//	rPivotMat.set_trans( -rPivot );
//	rPosMat.set_trans( rPos + rPivot );
	rPivotMat.set_trans( rPivot );
	rPosMat.set_trans( rPos );
	rRotMat.set_rot( m_f32Angle );
	rScaleMat.set_scale( rScale ) ;

	m_rTM = rPivotMat * rRotMat * rScaleMat * rPosMat;

	//
	// calc bounding box
	//
	float32		f32Width = 25;
	float32		f32Height = 25;
	Vector2C	rMin, rMax;
	Vector2C	rVec;

	// get the file width and height (actually half of it...
	ImportableImageI*	pImp = m_pAttribGizmo->get_fill_file( i32Time );
	if( pImp ) {
		f32Width = (float32)pImp->get_width() * 0.5f;
		f32Height = (float32)pImp->get_height() * 0.5f;
	}

	m_rVertices[0][0] = -f32Width;		// top-left
	m_rVertices[0][1] = -f32Height;

	m_rVertices[1][0] =  f32Width;		// top-right
	m_rVertices[1][1] = -f32Height;

	m_rVertices[2][0] =  f32Width;		// bottom-right
	m_rVertices[2][1] =  f32Height;

	m_rVertices[3][0] = -f32Width;		// bottom-left
	m_rVertices[3][1] =  f32Height;

	for( uint32 i = 0; i < 4; i++ ) {
		rVec = m_rTM * m_rVertices[i];
		m_rVertices[i] = rVec;

		if( !i ) {
			rMin = rMax = rVec;
		}
		else {
			if( rVec[0] < rMin[0] ) rMin[0] = rVec[0];
			if( rVec[1] < rMin[1] ) rMin[1] = rVec[1];
			if( rVec[0] > rMax[0] ) rMax[0] = rVec[0];
			if( rVec[1] > rMax[1] ) rMax[1] = rVec[1];
		}
	}

	// set
	m_rBBox[0] = rMin;
	m_rBBox[1] = rMax;

	// get fill color
	m_rFillColor = m_pAttribGizmo->get_fill_color( i32Time );

	// get outline color
	m_rOutlineColor = m_pAttribGizmo->get_outline_color( i32Time );

	// get outline width
	m_f32OutlineWidth = m_pAttribGizmo->get_outline_width( i32Time );

	// get rendermode
	m_i32RenderMode = m_pAttribGizmo->get_render_mode( i32Time );

	// get filtermode
	m_i32FilterMode = m_pAttribGizmo->get_filter_mode( i32Time );

	// get filtermode
	m_i32Shape = m_pAttribGizmo->get_shape( i32Time );




	OpenGLDeviceC*	pDevice = (OpenGLDeviceC*)pContext->query_interface( CLASS_OPENGL_DEVICEDRIVER );
	if( !pDevice )
		return;

	OpenGLViewportC*	pViewport = (OpenGLViewportC*)pDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );
	if( !pViewport )
		return;

	pViewport->set_ortho( m_rBBox, m_rBBox[0][0], m_rBBox[1][0], m_rBBox[1][1], m_rBBox[0][1] );

	if( pImp ) {
		int32	i32Flags = IMAGE_CLAMP;
		if( m_i32FilterMode == 0 )
			i32Flags |= IMAGE_LINEAR;
		else
			i32Flags |= IMAGE_NEAREST;
		pImp->bind_texture( pDevice, i32Flags );
		glEnable( GL_TEXTURE_2D );
	}
	else
		glDisable( GL_TEXTURE_2D );

	glDisable( GL_DEPTH_TEST );
	glEnable( GL_BLEND );

	if( m_i32RenderMode == 0 ) {
		// normal
		glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
	}
	else if( m_i32RenderMode == 1 ) {
		// add
		glBlendFunc( GL_SRC_ALPHA, GL_ONE );
	}
	else {
		// mult
		glBlendFunc( GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA );
	}


	glDepthMask( GL_FALSE );

	Vector2C	rBase = m_rVertices[0];
	Vector2C	rAxisX = m_rVertices[1] - m_rVertices[0];
	Vector2C	rAxisY = m_rVertices[2] - m_rVertices[1];

	if( m_i32Shape == 0 ) {

		//
		// rectangle
		//

		if( m_f32OutlineWidth != 0.0f ) {

			float32	f32Width = rAxisX.length();
			float32	f32Height = rAxisY.length();

			Vector2C	rAxisXDir = rAxisX / f32Width;
			Vector2C	rAxisYDir = rAxisY / f32Height;

			if( f32Width < m_f32OutlineWidth * 2 || f32Height < m_f32OutlineWidth * 2 ) {
				// we are all outline, baby!

				if( m_i32RenderMode == 2 )	// mult
					glColor4f( m_rOutlineColor[0] * m_rOutlineColor[3], m_rOutlineColor[1] * m_rOutlineColor[3], m_rOutlineColor[2] * m_rOutlineColor[3], m_rOutlineColor[3] );
				else
					glColor4f( m_rOutlineColor[0], m_rOutlineColor[1], m_rOutlineColor[2], m_rOutlineColor[3] );

				glDisable( GL_TEXTURE_2D );

				glBegin( GL_QUADS );

				glVertex2f( m_rVertices[0][0], m_rVertices[0][1] );
				glVertex2f( m_rVertices[1][0], m_rVertices[1][1] );
				glVertex2f( m_rVertices[2][0], m_rVertices[2][1] );
				glVertex2f( m_rVertices[3][0], m_rVertices[3][1] );

				glEnd();
			}
			else {

				Vector2C	rInnerVertices[4];
				rInnerVertices[0] = m_rVertices[0] + (rAxisXDir + rAxisYDir) * m_f32OutlineWidth;
				rInnerVertices[1] = m_rVertices[1] + (-rAxisXDir + rAxisYDir) * m_f32OutlineWidth;
				rInnerVertices[2] = m_rVertices[2] + (-rAxisXDir - rAxisYDir) * m_f32OutlineWidth;
				rInnerVertices[3] = m_rVertices[3] + (rAxisXDir - rAxisYDir) * m_f32OutlineWidth;

				// outline + fill
				if( m_rOutlineColor[3] != 0 ) {
					if( m_i32RenderMode == 2 ) // mult
						glColor4f( m_rOutlineColor[0] * m_rOutlineColor[3], m_rOutlineColor[1] * m_rOutlineColor[3], m_rOutlineColor[2] * m_rOutlineColor[3], m_rOutlineColor[3] );
					else
						glColor4f( m_rOutlineColor[0], m_rOutlineColor[1], m_rOutlineColor[2], m_rOutlineColor[3] );

					glDisable( GL_TEXTURE_2D );

					glBegin( GL_TRIANGLE_STRIP );

					glVertex2f( m_rVertices[0][0], m_rVertices[0][1] );
					glVertex2f( rInnerVertices[0][0], rInnerVertices[0][1] );

					glVertex2f( m_rVertices[1][0], m_rVertices[1][1] );
					glVertex2f( rInnerVertices[1][0], rInnerVertices[1][1] );

					glVertex2f( m_rVertices[2][0], m_rVertices[2][1] );
					glVertex2f( rInnerVertices[2][0], rInnerVertices[2][1] );

					glVertex2f( m_rVertices[3][0], m_rVertices[3][1] );
					glVertex2f( rInnerVertices[3][0], rInnerVertices[3][1] );

					glVertex2f( m_rVertices[0][0], m_rVertices[0][1] );
					glVertex2f( rInnerVertices[0][0], rInnerVertices[0][1] );

					glEnd();

				}

				if( m_rFillColor[3] != 0 ) {
					if( m_i32RenderMode == 2 ) // mult
						glColor4f( m_rFillColor[0] * m_rFillColor[3], m_rFillColor[1] * m_rFillColor[3], m_rFillColor[2] * m_rFillColor[3], m_rFillColor[3] );
					else
						glColor4f( m_rFillColor[0], m_rFillColor[1], m_rFillColor[2], m_rFillColor[3] );

					if( pImp )
						glEnable( GL_TEXTURE_2D );

					glBegin( GL_QUADS );

					glTexCoord2f( 0, 0 );
					glVertex2f( rInnerVertices[0][0], rInnerVertices[0][1] );
					
					glTexCoord2f( 1, 0 );
					glVertex2f( rInnerVertices[1][0], rInnerVertices[1][1] );
					
					glTexCoord2f( 1, 1 );
					glVertex2f( rInnerVertices[2][0], rInnerVertices[2][1] );

					glTexCoord2f( 0, 1 );
					glVertex2f( rInnerVertices[3][0], rInnerVertices[3][1] );

					glEnd();
				}
			}
		}
		else {
			// there's no outline to draw
			if( m_i32RenderMode == 2 ) // mult
				glColor4f( m_rFillColor[0] * m_rFillColor[3], m_rFillColor[1] * m_rFillColor[3], m_rFillColor[2] * m_rFillColor[3], m_rFillColor[3] );
			else
				glColor4f( m_rFillColor[0], m_rFillColor[1], m_rFillColor[2], m_rFillColor[3] );

			if( pImp )
				glEnable( GL_TEXTURE_2D );

			Vector2C	rTexCoord;

			// rectangle
			glBegin( GL_QUADS );

			glTexCoord2f( 0, 0 );
			glVertex2f( m_rVertices[0][0], m_rVertices[0][1] );
			
			glTexCoord2f( 1, 0 );
			glVertex2f( m_rVertices[1][0], m_rVertices[1][1] );
			
			glTexCoord2f( 1, 1 );
			glVertex2f( m_rVertices[2][0], m_rVertices[2][1] );

			glTexCoord2f( 0, 1 );
			glVertex2f( m_rVertices[3][0], m_rVertices[3][1] );

			glEnd();

		}
	}
	else {

		//
		// oval
		//

		if( m_f32OutlineWidth != 0.0f ) {

			float32	f32Width = rAxisX.length();
			float32	f32Height = rAxisY.length();

			Vector2C	rAxisXDir = rAxisX / f32Width;
			Vector2C	rAxisYDir = rAxisY / f32Height;

			if( f32Width < m_f32OutlineWidth * 2 || f32Height < m_f32OutlineWidth * 2 ) {
				// we are all outline, baby!

				if( m_i32RenderMode == 2 )	// mult
					glColor4f( m_rOutlineColor[0] * m_rOutlineColor[3], m_rOutlineColor[1] * m_rOutlineColor[3], m_rOutlineColor[2] * m_rOutlineColor[3], m_rOutlineColor[3] );
				else
					glColor4f( m_rOutlineColor[0], m_rOutlineColor[1], m_rOutlineColor[2], m_rOutlineColor[3] );

				glDisable( GL_TEXTURE_2D );

				glBegin( GL_TRIANGLE_FAN );

				float32	f32Angle = 0;

				for( uint32 i = 0; i < 64; i++ ) {
					float32	f32X = ((float32)cos( f32Angle ) + 1.0f) * 0.5f;
					float32	f32Y = ((float32)sin( f32Angle ) + 1.0f) * 0.5f;

					Vector2C	rPt = rBase + rAxisX * f32X + rAxisY * f32Y;

					glVertex2f( rPt[0], rPt[1] );

					f32Angle += (float32)M_PI * 2.0f / 64.0f;
				}

				glEnd();
			}
			else {

				Vector2C	rInnerVertices[4];
				rInnerVertices[0] = m_rVertices[0] + (rAxisXDir + rAxisYDir) * m_f32OutlineWidth;
				rInnerVertices[1] = m_rVertices[1] + (-rAxisXDir + rAxisYDir) * m_f32OutlineWidth;
				rInnerVertices[2] = m_rVertices[2] + (-rAxisXDir - rAxisYDir) * m_f32OutlineWidth;
				rInnerVertices[3] = m_rVertices[3] + (rAxisXDir - rAxisYDir) * m_f32OutlineWidth;

				Vector2C	rOuter[64];
				Vector2C	rInner[64];
				Vector2C	rTex[64];

				Vector2C	rInnerBase = rInnerVertices[0];
				Vector2C	rInnerAxisX = rInnerVertices[1] - rInnerVertices[0];
				Vector2C	rInnerAxisY = rInnerVertices[2] - rInnerVertices[1];

				float32	f32Angle = 0;

				// calc inner path and texture coords
				for( uint32 i = 0; i < 64; i++ ) {
					float32	f32X = ((float32)cos( f32Angle ) + 1.0f) * 0.5f;
					float32	f32Y = ((float32)sin( f32Angle ) + 1.0f) * 0.5f;

					rInner[i] = rInnerBase + rInnerAxisX * f32X + rInnerAxisY * f32Y;
					rTex[i][0] = f32X;
					rTex[i][1] = f32Y;

					f32Angle += (float32)M_PI * 2.0f / 64.0f;
				}

				// calc outer path by offsetting the inner path
				for( i = 0; i < 64; i++ ) {
					int32	i32Prev = (i - 1) & 63;
					int32	i32Next = (i + 1) & 63;
					Vector2C	rPrev = rInner[i] - rInner[i32Prev];
					Vector2C	rNext = rInner[i32Next] - rInner[i];
					Vector2C	rNorm = (Vector2C( rPrev[1], -rPrev[0] ) + Vector2C( rNext[1], -rNext[0] )) * 0.5f;
					rNorm = rNorm.normalize();

					rOuter[i] = rInner[i] + rNorm * m_f32OutlineWidth;
				}

				// outline

				if( m_i32RenderMode == 2 )	// mult
					glColor4f( m_rOutlineColor[0] * m_rOutlineColor[3], m_rOutlineColor[1] * m_rOutlineColor[3], m_rOutlineColor[2] * m_rOutlineColor[3], m_rOutlineColor[3] );
				else
					glColor4f( m_rOutlineColor[0], m_rOutlineColor[1], m_rOutlineColor[2], m_rOutlineColor[3] );

				glDisable( GL_TEXTURE_2D );

				glBegin( GL_TRIANGLE_STRIP );

				glVertex2f( rInner[63][0], rInner[63][1] );
				glVertex2f( rOuter[63][0], rOuter[63][1] );

				for( i = 0; i < 64; i++ ) {
					glVertex2f( rInner[i][0], rInner[i][1] );
					glVertex2f( rOuter[i][0], rOuter[i][1] );
				}

				glEnd();

				// fill

				if( m_i32RenderMode == 2 ) // mult
					glColor4f( m_rFillColor[0] * m_rFillColor[3], m_rFillColor[1] * m_rFillColor[3], m_rFillColor[2] * m_rFillColor[3], m_rFillColor[3] );
				else
					glColor4f( m_rFillColor[0], m_rFillColor[1], m_rFillColor[2], m_rFillColor[3] );

				if( pImp )
					glEnable( GL_TEXTURE_2D );


				glBegin( GL_TRIANGLE_FAN );

				for( i = 0; i < 64; i++ ) {
					glTexCoord2f( rTex[i][0], rTex[i][1] );
					glVertex2f( rInner[i][0], rInner[i][1] );
				}

				glEnd();

			}
		}
		else {
			if( m_i32RenderMode == 2 ) // mult
				glColor4f( m_rFillColor[0] * m_rFillColor[3], m_rFillColor[1] * m_rFillColor[3], m_rFillColor[2] * m_rFillColor[3], m_rFillColor[3] );
			else
				glColor4f( m_rFillColor[0], m_rFillColor[1], m_rFillColor[2], m_rFillColor[3] );

			if( pImp )
				glEnable( GL_TEXTURE_2D );

			glBegin( GL_TRIANGLE_FAN );

			float32	f32Angle = 0;

			for( uint32 i = 0; i < 64; i++ ) {
				float32	f32X = ((float32)cos( f32Angle ) + 1.0f) * 0.5f;
				float32	f32Y = ((float32)sin( f32Angle ) + 1.0f) * 0.5f;

				Vector2C	rPt = rBase + rAxisX * f32X + rAxisY * f32Y;

				glTexCoord2f( f32X, f32Y );
				glVertex2f( rPt[0], rPt[1] );

				f32Angle += (float32)M_PI * 2.0f / 64.0f;
			}

			glEnd();
		}
	}

	if( pImp )
		glDisable( GL_TEXTURE_2D );

	glDepthMask( GL_TRUE );
}


bool
TestEffectC::hit_test( const PajaTypes::Vector2C& rPoint )
{
//	if( m_i32Shape == 0 ) {
		// rectangle

		// point in polygon test
		// from c.g.a FAQ
		int		i, j;
		bool	bInside = false;

		for( i = 0, j = 4 - 1; i < 4; j = i++ ) {
			if( ( ((m_rVertices[i][1] <= rPoint[1]) && (rPoint[1] < m_rVertices[j][1])) ||
				((m_rVertices[j][1] <= rPoint[1]) && (rPoint[1] < m_rVertices[i][1])) ) &&
				(rPoint[0] < (m_rVertices[j][0] - m_rVertices[i][0]) * (rPoint[1] - m_rVertices[i][1]) / (m_rVertices[j][1] - m_rVertices[i][1]) + m_rVertices[i][0]) )

				bInside = !bInside;
		}

		return bInside;
//	}
//	else if( m_i32Shape == 1 ) {
//	}

	return false;
}

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

Matrix2C
TestEffectC::get_texture_matrix() const
{
	Matrix2C	TexMat;
	Vector2C	rBase = m_rVertices[0];
	Vector2C	rAxisX = m_rVertices[1] - m_rVertices[0];
	Vector2C	rAxisY = m_rVertices[2] - m_rVertices[1];

	TexMat[0] = rAxisX;
	TexMat[1] = rAxisY;
	TexMat[2] = rBase;

	return TexMat.invert();
}

enum TestEffectChunksE {
	CHUNK_TESTEFFECT_BASE =			0x1000,
	CHUNK_TESTEFFECT_GIZMO =		0x2000,	// old
	CHUNK_TESTEFFECT_TRANSGIZMO =	0x3000,
	CHUNK_TESTEFFECT_ATTRIBGIZMO =	0x4000,
};

const uint32	TESTEFFECT_VERSION = 1;

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

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

	// transform
	pSave->begin_chunk( CHUNK_TESTEFFECT_TRANSGIZMO, TESTEFFECT_VERSION );
		ui32Error = m_pTransGizmo->save( pSave );
	pSave->end_chunk();

	// attribute
	pSave->begin_chunk( CHUNK_TESTEFFECT_ATTRIBGIZMO, TESTEFFECT_VERSION );
		ui32Error = m_pAttribGizmo->save( pSave );
	pSave->end_chunk();

	return ui32Error;
}

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

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

		switch( pLoad->get_chunk_id() ) {
		case CHUNK_TESTEFFECT_BASE:
			{
				if( pLoad->get_chunk_version() == TESTEFFECT_VERSION )
					ui32Error = EffectI::load( pLoad );
			}
			break;

		case CHUNK_TESTEFFECT_GIZMO:
			{
				// load old gizmo and convert it to the new structure
				if( pLoad->get_chunk_version() == TESTEFFECT_VERSION ) {
					m_pTestGizmo = TestGizmoC::create_new( this, 0 );
					ui32Error = m_pTestGizmo->load( pLoad );
				}
			}
			break;

		case CHUNK_TESTEFFECT_TRANSGIZMO:
			{
				if( pLoad->get_chunk_version() == TESTEFFECT_VERSION )
					ui32Error = m_pTransGizmo->load( pLoad );
			}
			break;

		case CHUNK_TESTEFFECT_ATTRIBGIZMO:
			{
				if( pLoad->get_chunk_version() == TESTEFFECT_VERSION )
					ui32Error = m_pAttribGizmo->load( pLoad );
			}
			break;

		default:
			assert( 0 );
		}

		pLoad->close_chunk();

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

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

	return IO_OK;
}


//
// TARGA import
//


TGAImportC::TGAImportC() :
	m_pData( 0 ),
	m_i32Width( 0 ),
	m_i32Height( 0 )
{
	for( uint32 i = 0; i < 4; i++ )
		m_ui32TextureIds[i] = 0;
}

TGAImportC::TGAImportC( EditableI* pOriginal ) :
	ImportableImageI( pOriginal ),
	m_pData( 0 ),
	m_i32Width( 0 ),
	m_i32Height( 0 )
{
	for( uint32 i = 0; i < 4; i++ )
		m_ui32TextureIds[i] = 0;
}

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

	// delete creted textures
	for( uint32 i = 0; i < 4; i++ )
		if( m_ui32TextureIds[i] )
			glDeleteTextures( 1, &m_ui32TextureIds[i] );

	delete [] m_pData;
}

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

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

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

void
TGAImportC::copy( EditableI* pEditable )
{
	// empty
}

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

	for( uint32 i = 0; i < 4; i++ )
		m_ui32TextureIds[i] = pFile->m_ui32TextureIds[i];

	m_pData = pFile->m_pData;
	m_i32Width = pFile->m_i32Width;
	m_i32Height = pFile->m_i32Height;
	m_i32Bpp = pFile->m_i32Bpp;
	m_sFileName = pFile->m_sFileName;
}


// the interface

// returns the name of the file this importable refers to
const char*
TGAImportC::get_filename()
{
	return m_sFileName.c_str();
}

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



#define TGA_ALPHA_NONE 0
#define TGA_ALPHA_IGNORE 1
#define TGA_ALPHA_RETAIN 2
#define TGA_ALPHA_HASALPHA 3
#define TGA_ALPHA_PREMULT 4



// Load an 8-bit gray TGA file

bool
TGAImportC::load_8bit_pal_TGA( FILE* fp, TGAHeaderS& rHdr )
{

	m_pData = new uint8[m_i32Width * m_i32Height * 3];
	m_i32Bpp = 24;

	//-- Bypass the image identification field, if present -------------------
	if( rHdr.idlen ) {
		if( fseek( fp,(long)rHdr.idlen, SEEK_CUR )!= 0 )
			return false;
	}

	if( !rHdr.cmlen )
		return false;

	uint8	ui8Pal[256 * 3];
	uint8	ui8PalTmp[256 * 4];
	memset( ui8Pal, 0, 256 * 3 );


	// Load up the palette
	switch( rHdr.cmes ) {
	case 15:
	case 16:
		{
			if( !fread( ui8PalTmp, rHdr.cmlen * 2, 1, fp ) )
				return false;

			uint16*	pPal = (uint16*)ui8PalTmp;

			for( int32 i = 0; i < rHdr.cmlen; i++ ) {
				ui8Pal[i * 3 + 0] = (*pPal & 0x7c00) >> 7;
				ui8Pal[i * 3 + 1] = (*pPal & 0x03e0) >> 2;
				ui8Pal[i * 3 + 2] = (*pPal & 0x001f) << 3;
				pPal++;
			}
		}
		break;

	case 24:
		{
			if( !fread( ui8PalTmp, rHdr.cmlen * 3, 1, fp ) )
				return false;

			for( int32 i = 0; i < rHdr.cmlen; i++ ) {
				ui8Pal[i * 3 + 0] = ui8PalTmp[i * 3 + 2];
				ui8Pal[i * 3 + 1] = ui8PalTmp[i * 3 + 1];
				ui8Pal[i * 3 + 2] = ui8PalTmp[i * 3 + 0];
			}
		}
		break;

	case 32:
		{
			if( !fread( ui8PalTmp, rHdr.cmlen * 4, 1, fp ) )
				return false;

			for( int32 i = 0; i < rHdr.cmlen; i++ ) {
				ui8Pal[i * 3 + 0] = ui8PalTmp[i * 4 + 2];
				ui8Pal[i * 3 + 1] = ui8PalTmp[i * 4 + 1];
				ui8Pal[i * 3 + 2] = ui8PalTmp[i * 4 + 0];
			}
		}
		break;
	}

	//-- Read Image File -----------------------------------------------------
	switch( rHdr.imgtype ) {

	//-- Uncompressed Palette -----------------------------
	case 1:
		{
			uint8*	pDest = m_pData;
			uint8	ui8Pixel;
			for( int32 int32Y = 0; int32Y < m_i32Height; int32Y++ ) {

				for( int32 i32X = 0; i32X < m_i32Width; i32X++ ) {
					if( !fread( &ui8Pixel, 1, 1, fp ) )
						return false;
					ui8Pixel -= rHdr.cmorg;
					pDest[i32X * 3 + 0] = ui8Pal[ui8Pixel * 3 + 0];
					pDest[i32X * 3 + 1] = ui8Pal[ui8Pixel * 3 + 1];
					pDest[i32X * 3 + 2] = ui8Pal[ui8Pixel * 3 + 2];
				}

				pDest += m_i32Width * 3;
			}
		}
		break;
	
	//-- Compressed Palette -------------------------------

	case 9:
		{
			int32	i32X = 0;
			int32	i32Y = 0;
			uint8	ui8Rle;
			uint8	ui8Pixel;
			uint8*	pDest = m_pData;
			
			while( 1 ) {
				
				if( !fread( &ui8Rle, 1, 1, fp ) )
					return false;
				
				if( ui8Rle > 127 ) {
					//-- Compressed Block
					ui8Rle -= 127;

					if( !fread( &ui8Pixel, 1, 1, fp ) )
						return false;

					for( int32 i = 0; i < ui8Rle; i++ ) {

						ui8Pixel -= rHdr.cmorg;
						pDest[i32X++] = ui8Pal[ui8Pixel * 3 + 0];
						pDest[i32X++] = ui8Pal[ui8Pixel * 3 + 1];
						pDest[i32X++] = ui8Pal[ui8Pixel * 3 + 2];

						if( i32X >= m_i32Width * 3 ) {
							i32X = 0;
							i32Y++;
							if( i32Y >= m_i32Height )
								break;
							pDest += m_i32Width * 3;
						}
					}
				}
				else {
					//-- Uncompressed Block
					ui8Rle++;

					for( int32 i = 0; i < ui8Rle; i++ ) {

						if( !fread( &ui8Pixel, 1, 1, fp ) )
							return false;

						pDest[i32X++] = ui8Pal[ui8Pixel * 3 + 0];
						pDest[i32X++] = ui8Pal[ui8Pixel * 3 + 1];
						pDest[i32X++] = ui8Pal[ui8Pixel * 3 + 2];

						if( i32X >= m_i32Width * 3 ) {
							i32X = 0;
							i32Y++;
							if( i32Y >= m_i32Height )
								break;
							pDest += m_i32Width * 3;
						}
					}
				}
			}
		}
		break;
	}

	return true;
}



// Load an 8-bit gray TGA file

bool
TGAImportC::load_8bit_gray_TGA( FILE* fp, TGAHeaderS& rHdr )
{

	m_pData = new uint8[m_i32Width * m_i32Height];
	m_i32Bpp = 8;

	//-- Bypass the image identification field, if present -------------------
	if( rHdr.idlen ) {
		if( fseek( fp,(long)rHdr.idlen, SEEK_CUR )!= 0 )
			return false;
	}

	//-- Bypass the color map, if present ------------------------------------
	if( rHdr.cmlen ) {
		if( fseek( fp, (long)rHdr.cmlen * (long)((rHdr.cmes + 7) / 8), SEEK_CUR ) != 0 )
			return false;
	}

	//-- Read Image File -----------------------------------------------------
	switch( rHdr.imgtype ) {

	//-- Uncompressed Gray -----------------------------
	case 3:
		{
			uint8*	pDest = m_pData;
			for( int32 int32Y = 0; int32Y < m_i32Height; int32Y++ ) {

				if( !fread( pDest, m_i32Width, 1, fp ) )
					return false;

				pDest += m_i32Width;
			}
		}
		break;
	
	//-- Compressed Gray -------------------------------

	case 11:
		{
			int32	i32X = 0;
			int32	i32Y = 0;
			uint8	ui8Rle;
			uint8	ui8Pixel;
			uint8*	pDest = m_pData;
			
			while( 1 ) {
				
				if( !fread( &ui8Rle, 1, 1, fp ) )
					return false;
				
				if( ui8Rle > 127 ) {
					//-- Compressed Block
					ui8Rle -= 127;

					if( !fread( &ui8Pixel, 1, 1, fp ) )
						return false;

					for( int32 i = 0; i < ui8Rle; i++ ) {

						pDest[i32X++] = ui8Pixel;

						if( i32X >= m_i32Width ) {
							i32X = 0;
							i32Y++;
							if( i32Y >= m_i32Height )
								break;
							pDest += m_i32Width;
						}
					}
				}
				else {
					//-- Uncompressed Block
					ui8Rle++;

					for( int32 i = 0; i < ui8Rle; i++ ) {

						if( !fread( &ui8Pixel, 1, 1, fp ) )
							return false;

						pDest[i32X++] = ui8Pixel;

						if( i32X >= m_i32Width ) {
							i32X = 0;
							i32Y++;
							if( i32Y >= m_i32Height )
								break;
							pDest += m_i32Width;
						}
					}
				}
			}
		}
		break;
	}

	return true;
}

// Load an 16-bit TGA file

bool
TGAImportC::load_16bit_TGA( FILE* fp, TGAHeaderS& rHdr )
{

	m_pData = new uint8[m_i32Width * m_i32Height * 4];
	m_i32Bpp = 32;

	//-- Bypass the image identification field, if present -------------------
	if( rHdr.idlen ) {
		if( fseek( fp,(long)rHdr.idlen, SEEK_CUR )!= 0 )
			return false;
	}

	//-- Bypass the color map, if present ------------------------------------
	if( rHdr.cmlen ) {
		if( fseek( fp, (long)rHdr.cmlen * (long)((rHdr.cmes + 7) / 8), SEEK_CUR ) != 0 )
			return false;
	}

//	OutputDebugString( "16bit ok\n" );

	//-- Read Image File -----------------------------------------------------
	switch( rHdr.imgtype ) {

	//-- Uncompressed RGB -----------------------------
	case 2:
		{
			uint8*	pDest = m_pData;
			for( int32 int32Y = 0; int32Y < m_i32Height; int32Y++ ) {
				uint16	ui16Pixel;
				for( int32 i32X = 0; i32X < m_i32Width; i32X++ ) {
					if( !fread( &ui16Pixel, 2, 1, fp ) )
						return false;
					pDest[i32X * 4 + 0] = (ui16Pixel & 0x7c00) >> 7;
					pDest[i32X * 4 + 1] = (ui16Pixel & 0x03e0) >> 2;
					pDest[i32X * 4 + 2] = (ui16Pixel & 0x001f) << 3;
					pDest[i32X * 4 + 3] = (ui16Pixel & 0x8000) ? 0 : 0xff;
				}
				pDest += m_i32Width * 4;
			}
		}
		break;
	
	//-- Compressed RGB -------------------------------

	case 10:
		{
			int32	i32X = 0;
			int32	i32Y = 0;
			uint8	ui8Rle;
			uint16	ui16Pixel;
			uint8*	pDest = m_pData;
			
			while( 1 ) {
				
				if( !fread( &ui8Rle, 1, 1, fp ) )
					return false;
				
				if( ui8Rle > 127 ) {
					//-- Compressed Block
					ui8Rle -= 127;

					if( !fread( &ui16Pixel, 2, 1, fp ) )
						return false;

					for( int32 i = 0; i < ui8Rle; i++ ) {

						pDest[i32X++] = (ui16Pixel & 0x7c00) >> 7;
						pDest[i32X++] = (ui16Pixel & 0x03e0) >> 2;
						pDest[i32X++] = (ui16Pixel & 0x001f) << 3;
						pDest[i32X++] = (ui16Pixel & 0x8000) ? 0 : 0xff;

						if( i32X >= m_i32Width * 4 ) {
							i32X = 0;
							i32Y++;
							if( i32Y >= m_i32Height )
								break;
							pDest += m_i32Width * 4;
						}
					}
				}
				else {
					//-- Uncompressed Block
					ui8Rle++;

					for( int32 i = 0; i < ui8Rle; i++ ) {

						if( !fread( &ui16Pixel, 2, 1, fp ) )
							return false;

						pDest[i32X++] = (ui16Pixel & 0x7c00) >> 7;
						pDest[i32X++] = (ui16Pixel & 0x03e0) >> 2;
						pDest[i32X++] = (ui16Pixel & 0x001f) << 3;
						pDest[i32X++] = (ui16Pixel & 0x8000) ? 0xff : 0;

						if( i32X >= m_i32Width * 4 ) {
							i32X = 0;
							i32Y++;
							if( i32Y >= m_i32Height )
								break;
							pDest += m_i32Width * 4;
						}
					}
				}
			}
		}
		break;
	}

	return true;
}

// Load an 24-bit TGA file

bool
TGAImportC::load_24bit_TGA( FILE* fp, TGAHeaderS& rHdr )
{

	m_pData = new uint8[m_i32Width * m_i32Height *3];
	m_i32Bpp = 24;

	//-- Bypass the image identification field, if present -------------------
	if( rHdr.idlen ) {
		if( fseek( fp,(long)rHdr.idlen, SEEK_CUR )!= 0 )
			return false;
	}

	//-- Bypass the color map, if present ------------------------------------
	if( rHdr.cmlen ) {
		if( fseek( fp, (long)rHdr.cmlen * (long)((rHdr.cmes + 7) / 8), SEEK_CUR ) != 0 )
			return false;
	}

	//-- Read Image File -----------------------------------------------------
	switch( rHdr.imgtype ) {

	//-- Uncompressed RGB -----------------------------
	case 2:
		{
			uint8*	pDest = m_pData;
			for( int32 int32Y = 0; int32Y < m_i32Height; int32Y++ ) {
				if( !fread( pDest, m_i32Width * 3, 1, fp ) )
					return false;

				// swap r & b
				for( int32 i = 0; i < m_i32Width; i++ ) {
					uint8	ui8Tmp = pDest[i * 3];
					pDest[i * 3] = pDest[i * 3 + 2];
					pDest[i * 3 + 2] = ui8Tmp;
				}

				pDest += m_i32Width * 3;
			}
		}
		break;
	
	//-- Compressed RGB -------------------------------

	case 10:
		{
			int32	i32X = 0;
			int32	i32Y = 0;
			uint8	ui8Rle;
			uint8	ui8Pixel[3];
			uint8*	pDest = m_pData;
			
			while( 1 ) {
				
				if( !fread( &ui8Rle, 1, 1, fp ) )
					return false;
				
				if( ui8Rle > 127 ) {
					//-- Compressed Block
					ui8Rle -= 127;

					if( !fread( ui8Pixel, 3, 1, fp ) )
						return false;

					for( int32 i = 0; i < ui8Rle; i++ ) {
						pDest[i32X++] = ui8Pixel[2];
						pDest[i32X++] = ui8Pixel[1];
						pDest[i32X++] = ui8Pixel[0];
						if( i32X >= m_i32Width * 3 ) {
							i32X = 0;
							i32Y++;
							if( i32Y >= m_i32Height )
								break;
							pDest += m_i32Width * 3;
						}
					}
				}
				else {
					//-- Uncompressed Block
					ui8Rle++;

					for( int32 i = 0; i < ui8Rle; i++ ) {

						if( !fread( ui8Pixel, 3, 1, fp ) )
							return false;

						pDest[i32X++] = ui8Pixel[2];
						pDest[i32X++] = ui8Pixel[1];
						pDest[i32X++] = ui8Pixel[0];

						if( i32X >= m_i32Width * 3 ) {
							i32X = 0;
							i32Y++;
							if( i32Y >= m_i32Height )
								break;
							pDest += m_i32Width * 3;
						}
					}
				}
			}
		}
		break;
	}

	return true;
}



// Load an 32-bit TGA file

bool
TGAImportC::load_32bit_TGA( FILE* fp, TGAHeaderS& rHdr )
{

	m_pData = new uint8[m_i32Width * m_i32Height * 4];
	m_i32Bpp = 32;

	//-- Bypass the image identification field, if present -------------------
	if( rHdr.idlen ) {
		if( fseek( fp,(long)rHdr.idlen, SEEK_CUR )!= 0 )
			return false;
	}

	//-- Bypass the color map, if present ------------------------------------
	if( rHdr.cmlen ) {
		if( fseek( fp, (long)rHdr.cmlen * (long)((rHdr.cmes + 7) / 8), SEEK_CUR ) != 0 )
			return false;
	}

	//-- Read Image File -----------------------------------------------------
	switch( rHdr.imgtype ) {

	//-- Uncompressed RGB -----------------------------
	case 2:
		{
			uint8*	pDest = m_pData;
			for( int32 int32Y = 0; int32Y < m_i32Height; int32Y++ ) {
				if( !fread( pDest, m_i32Width * 4, 1, fp ) )
					return false;

				// swap r & b
				for( int32 i = 0; i < m_i32Width; i++ ) {
					uint8	ui8Tmp = pDest[i * 4];
					pDest[i * 4] = pDest[i * 4 + 2];
					pDest[i * 4 + 2] = ui8Tmp;
					pDest[i * 4 + 3] = pDest[i * 4 + 3];
				}

				pDest += m_i32Width * 4;
			}
		}
		break;
	
	//-- Compressed RGB -------------------------------

	case 10:
		{
			int32	i32X = 0;
			int32	i32Y = 0;
			uint8	ui8Rle;
			uint8	ui8Pixel[4];
			uint8*	pDest = m_pData;
			
			while( 1 ) {
				
				if( !fread( &ui8Rle, 1, 1, fp ) )
					return false;
				
				if( ui8Rle > 127 ) {
					//-- Compressed Block
					ui8Rle -= 127;

					if( !fread( ui8Pixel, 4, 1, fp ) )
						return false;

					for( int32 i = 0; i < ui8Rle; i++ ) {
						pDest[i32X++] = ui8Pixel[2];
						pDest[i32X++] = ui8Pixel[1];
						pDest[i32X++] = ui8Pixel[0];
						pDest[i32X++] = ui8Pixel[3];
						if( i32X >= m_i32Width * 4 ) {
							i32X = 0;
							i32Y++;
							if( i32Y >= m_i32Height )
								break;
							pDest += m_i32Width * 4;
						}
					}
				}
				else {
					//-- Uncompressed Block
					ui8Rle++;

					for( int32 i = 0; i < ui8Rle; i++ ) {

						if( !fread( ui8Pixel, 4, 1, fp ) )
							return false;

						pDest[i32X++] = ui8Pixel[2];
						pDest[i32X++] = ui8Pixel[1];
						pDest[i32X++] = ui8Pixel[0];
						pDest[i32X++] = ui8Pixel[3];

						if( i32X >= m_i32Width * 4 ) {
							i32X = 0;
							i32Y++;
							if( i32Y >= m_i32Height )
								break;
							pDest += m_i32Width * 4;
						}
					}
				}
			}
		}
		break;
	}

	return true;
}


// loads the file
bool
TGAImportC::load_file( const char* szName, DemoInterfaceC* pInterface )
{
	m_pDemoInterface = pInterface;

	FILE*		fp;
	TGAHeaderS	rHeader;

	if( (fp = fopen( pInterface->get_absolute_path( szName ), "rb" )) == 0 ) {
		return false;
	}

	m_sFileName = szName;

	// Return to the beginning of the file
	fseek( fp, 0L, SEEK_SET );

	if( fread( &rHeader, sizeof( TGAHeaderS ), 1, fp ) != 1 )
		return false;

	bool	bHFlip = (rHeader.desc & 0x10) ? true : false;		// Need hflip
	bool	bVFlip = (rHeader.desc & 0x20) ? true : false;		// Need vflip
	rHeader.desc &= 0xcf;										// Mask off flip bits

	m_i32Width = rHeader.width;
	m_i32Height = rHeader.height;

	delete [] m_pData;
	m_pData = 0;

	switch( rHeader.pixsize ) {
	case 8:
		{
			if( rHeader.desc != 0 && rHeader.desc != 1 && rHeader.desc != 8 )
				return false;
			switch( rHeader.imgtype ) {
			case 1:
			case 9:
				load_8bit_pal_TGA( fp, rHeader );
				break;
			case 3:
			case 11:
				load_8bit_gray_TGA( fp, rHeader );
				break;
			}
		}
		break;
	case 16:
		{
			if( rHeader.desc != 0 && rHeader.desc != 1 ) {
//				OutputDebugString( "plaaplaa\n" );
				return false;
			}
			load_16bit_TGA( fp, rHeader );
		}
		break;
	case 24:
		{
			if( rHeader.desc != 0 )
				return false;
			load_24bit_TGA( fp, rHeader );
		}
		break;
	case 32:
		{
			load_32bit_TGA( fp, rHeader );
		}
		break;
	default:
//		OutputDebugString( "invalid bitdepth\n" );
		return false;
	}

//	char	szMsg[256];
//	_snprintf( szMsg, 255, "size: %d x %d x 24bit\n", m_i32Width, m_i32Height );
//	OutputDebugString( szMsg );

	fclose( fp );

	//-- Perform clean-up operations!  
	if( m_pData ) {

		if( bHFlip ) {

//			OutputDebugString( "h-flip\n" );

			if( m_i32Bpp == 8 ) {
				uint8*	pTmpLine = new uint8[m_i32Width];
				uint8*	pDest = m_pData;

				for( int32 i = 0; i < m_i32Height; i++ ) {
					memcpy( pTmpLine, pDest, m_i32Width );
					for( int32 j = 0; j < m_i32Width; j++ ) {
						pDest[j] = pTmpLine[((m_i32Width - 1) - j)];
					}
					pDest += m_i32Width;
				}

				delete pTmpLine;
			}
			else if( m_i32Bpp == 24 ) {
				uint8*	pTmpLine = new uint8[m_i32Width * 3];
				uint8*	pDest = m_pData;

				for( int32 i = 0; i < m_i32Height; i++ ) {
					memcpy( pTmpLine, pDest, m_i32Width * 3 );
					for( int32 j = 0; j < m_i32Width; j++ ) {
						pDest[j * 3] = pTmpLine[((m_i32Width - 1) - j) * 3];
						pDest[j * 3 + 1] = pTmpLine[((m_i32Width - 1) - j) * 3 + 1];
						pDest[j * 3 + 2] = pTmpLine[((m_i32Width - 1) - j) * 3 + 2];
					}
					pDest += m_i32Width * 3;
				}

				delete pTmpLine;
			}
			else if( m_i32Bpp == 32 ) {
				uint8*	pTmpLine = new uint8[m_i32Width * 4];
				uint8*	pDest = m_pData;

				for( int32 i = 0; i < m_i32Height; i++ ) {
					memcpy( pTmpLine, pDest, m_i32Width * 4 );
					for( int32 j = 0; j < m_i32Width; j++ ) {
						pDest[j * 4 + 0] = pTmpLine[((m_i32Width - 1) - j) * 4 + 0];
						pDest[j * 4 + 1] = pTmpLine[((m_i32Width - 1) - j) * 4 + 1];
						pDest[j * 4 + 2] = pTmpLine[((m_i32Width - 1) - j) * 4 + 2];
						pDest[j * 4 + 3] = pTmpLine[((m_i32Width - 1) - j) * 4 + 3];
					}
					pDest += m_i32Width * 4;
				}

				delete pTmpLine;
			}

		}

		if( bVFlip ) {

			if( m_i32Bpp == 8 ) {
				uint8*	pTmpLineTop = new uint8[m_i32Width];
				uint8*	pTmpLineBottom = new uint8[m_i32Width];

				for( uint32 ui32YTop = 0, ui32YBot = m_i32Height - 1; ui32YTop < ui32YBot; ui32YTop++, ui32YBot-- ) {
					memcpy( pTmpLineTop, m_pData + (ui32YTop * m_i32Width), m_i32Width );
					memcpy( pTmpLineBottom, m_pData + (ui32YBot * m_i32Width), m_i32Width );

					memcpy( m_pData + (ui32YTop * m_i32Width), pTmpLineBottom, m_i32Width );
					memcpy( m_pData + (ui32YBot * m_i32Width), pTmpLineTop, m_i32Width );
				}

				delete [] pTmpLineTop;
				delete [] pTmpLineBottom;
			}
			else if( m_i32Bpp == 24 ) {
				uint8*	pTmpLineTop = new uint8[m_i32Width * 3];
				uint8*	pTmpLineBottom = new uint8[m_i32Width * 3];

				for( uint32 ui32YTop = 0, ui32YBot = m_i32Height - 1; ui32YTop < ui32YBot; ui32YTop++, ui32YBot-- ) {
					memcpy( pTmpLineTop, m_pData + (ui32YTop * m_i32Width * 3), m_i32Width * 3 );
					memcpy( pTmpLineBottom, m_pData + (ui32YBot * m_i32Width * 3), m_i32Width * 3 );

					memcpy( m_pData + (ui32YTop * m_i32Width * 3), pTmpLineBottom, m_i32Width * 3 );
					memcpy( m_pData + (ui32YBot * m_i32Width * 3), pTmpLineTop, m_i32Width * 3 );
				}

				delete [] pTmpLineTop;
				delete [] pTmpLineBottom;
			}
			else if( m_i32Bpp == 32 ) {
				uint8*	pTmpLineTop = new uint8[m_i32Width * 4];
				uint8*	pTmpLineBottom = new uint8[m_i32Width * 4];

				for( uint32 ui32YTop = 0, ui32YBot = m_i32Height - 1; ui32YTop < ui32YBot; ui32YTop++, ui32YBot-- ) {
					memcpy( pTmpLineTop, m_pData + (ui32YTop * m_i32Width * 4), m_i32Width * 4 );
					memcpy( pTmpLineBottom, m_pData + (ui32YBot * m_i32Width * 4), m_i32Width * 4 );

					memcpy( m_pData + (ui32YTop * m_i32Width * 4), pTmpLineBottom, m_i32Width * 4 );
					memcpy( m_pData + (ui32YBot * m_i32Width * 4), pTmpLineTop, m_i32Width * 4 );
				}

				delete [] pTmpLineTop;
				delete [] pTmpLineBottom;
			}
		}
	}


	return true;
}

void
TGAImportC::initialize( uint32 ui32Reason, DemoInterfaceC* pInterface )
{
	ImportableI::initialize( ui32Reason, pInterface );

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

	OpenGLDeviceC*	pDevice = (OpenGLDeviceC*)pContext->query_interface( CLASS_OPENGL_DEVICEDRIVER );
	if( !pDevice )
		return;

	if( ui32Reason == INIT_DEVICE_CHANGED ) {

		if( pDevice->get_state() == DEVICE_STATE_SHUTTINGDOWN ) {
			// Delete textures
			for( uint32 i = 0; i < 4; i++ ) {
				if( m_ui32TextureIds[i] ) {
					glDeleteTextures( 1, &m_ui32TextureIds[i] );
					m_ui32TextureIds[i] = 0;
				}
			}
		}

	}

	if( ui32Reason == INIT_INITIAL_UPDATE ) {
		if( !m_ui32TextureIds[0] )
			upload_texture();
	}
}

ClassIdC
TGAImportC::get_class_id()
{
	return CLASS_TGA_IMPORT;
}

const char*
TGAImportC::get_class_name()
{
	return "TGA Image";
}


// interface for this class

float32
TGAImportC::get_width()
{
	return (float32)m_i32Width;
}

float32
TGAImportC::get_height()
{
	return (float32)m_i32Height;
}

int32
TGAImportC::get_data_width()
{
	return m_i32Width;
}

int32
TGAImportC::get_data_height()
{
	return m_i32Height;
}

int32
TGAImportC::get_data_pitch()
{
	return m_i32Width;
}

int32
TGAImportC::get_data_bpp()
{
	return m_i32Bpp;
}

uint8*
TGAImportC::get_data()
{
	return m_pData;
}


inline
uint32
lowest_bit_mask( uint32 v )
{
	return (v & -v);
}

static
uint32
ceil_power2( uint32 ui32Num )
{
	uint32	i = lowest_bit_mask( ui32Num );
	while( i < ui32Num )
		i <<= 1;
	return i;
}

static
int32
nearest_power2( int32 i32Num )
{
	int32	i = ceil_power2( i32Num );	// bigger
	int32	j = i / 2;					// smaller

	int32	i32DiffI = i - i32Num;
	int32	i32DiffJ = i32Num - j;

	// diff J has to be twice as little as diffI to be chosen.
	i32DiffI /= 2;

	if( i32DiffJ < i32DiffI )
		return j;

	return i;
}

void
TGAImportC::upload_texture()
{
	glGenTextures( 1, &m_ui32TextureIds[0] );
	glBindTexture( GL_TEXTURE_2D, m_ui32TextureIds[0] );
	glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );

	uint8*	pResampleData = 0;

	int32	i32Width = m_i32Width;
	int32	i32Height = m_i32Height;
	uint8*	pData = m_pData;

	int32	i32WidthPow2 = nearest_power2( i32Width );
	int32	i32HeightPow2 = nearest_power2( i32Height );

	if( i32WidthPow2 != m_i32Width || i32HeightPow2 != m_i32Height ) {
		pResampleData = new uint8[i32WidthPow2 * i32HeightPow2 * (m_i32Bpp / 8)];
		ImageResampleC::resample_bilinear( m_i32Bpp, m_pData, m_i32Width, m_i32Height, pResampleData, i32WidthPow2, i32HeightPow2 );
		pData = pResampleData;
		i32Width = i32WidthPow2;
		i32Height = i32HeightPow2;
	}

	if( m_i32Bpp == 8 ) {
		glTexImage2D( GL_TEXTURE_2D, 0, GL_ALPHA , i32Width, i32Height, 0, GL_ALPHA , GL_UNSIGNED_BYTE, pData );
	}
	else if( m_i32Bpp == 24 ) {
		glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, i32Width, i32Height, 0, GL_RGB, GL_UNSIGNED_BYTE, pData );
	}
	else if( m_i32Bpp ==32 ) {
		glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, i32Width, i32Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pData );
	}

	if( pResampleData )
		delete [] pResampleData;
}

void
TGAImportC::bind_texture( DeviceInterfaceI* pInterface, uint32 ui32Properties )
{
	if( pInterface->get_class_id() != CLASS_OPENGL_DEVICEDRIVER )
		return;

	if( !m_ui32TextureIds[0] ) {
		upload_texture();
	}
	else
		glBindTexture( GL_TEXTURE_2D, m_ui32TextureIds[0] );

	if( ui32Properties & IMAGE_LINEAR ) {
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	}
	else {
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
	}

	if( ui32Properties & IMAGE_CLAMP ) {
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
	}
	else {
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
	}

}

const char*
TGAImportC::get_info()
{
	static char	szInfo[256] = "";
	char*	szType;
	if( m_i32Bpp == 8 )
		szType = "ALPHA";
	else if( m_i32Bpp == 16 )
		szType = "RGB";
	else if( m_i32Bpp == 24 )
		szType = "RGB";
	else if( m_i32Bpp == 32 )
		szType = "RGBA";

	_snprintf( szInfo, 255, "%s %d x %d x %dbit", szType, m_i32Width, m_i32Height, m_i32Bpp );
	return szInfo;
}

ClassIdC
TGAImportC::get_default_effect()
{
	return CLASS_IMAGE_EFFECT;
}

void
TGAImportC::eval_state( int32 i32Time )
{
	// empty
}

int32
TGAImportC::get_duration()
{
	return -1;
}

float32
TGAImportC::get_start_label()
{
	return 0;
}

float32
TGAImportC::get_end_label()
{
	return 0;
}


enum TGAImportChunksE {
	CHUNK_TGAIMPORT_BASE =	0x1000,
	CHUNK_TGAIMPORT_DATA =	0x2000,
};

const uint32	TGAIMPORT_VERSION_1 = 1;
const uint32	TGAIMPORT_VERSION = 2;

uint32
TGAImportC::save( SaveC* pSave )
{
	uint32		ui32Error = IO_OK;
	uint8		ui8Tmp;
	std::string	sStr;

	// file base
	pSave->begin_chunk( CHUNK_TGAIMPORT_BASE, TGAIMPORT_VERSION );
		sStr = m_sFileName;
		if( sStr.size() > 255 )
			sStr.resize( 255 );
		ui32Error = pSave->write_str( sStr.c_str() );
	pSave->end_chunk();

	// file data
	pSave->begin_chunk( CHUNK_TGAIMPORT_DATA, TGAIMPORT_VERSION );
		ui32Error = pSave->write( &m_i32Width, sizeof( m_i32Width ) );
		ui32Error = pSave->write( &m_i32Height, sizeof( m_i32Height ) );
		ui8Tmp = (uint8)m_i32Bpp;
		ui32Error = pSave->write( &ui8Tmp, sizeof( ui8Tmp ) );

		//
		// compress
		//
		int32	i32DataSize = m_i32Width * m_i32Height * (m_i32Bpp / 8);
		uint32	ui32CompressedSize;
		uint8*	pCompressed = new uint8[i32DataSize + i32DataSize / 64 + 16 + 3];
		long*	pWorkmem = new long[((LZO1X_1_MEM_COMPRESS) + (sizeof(long) - 1)) / sizeof(long)];

		if( lzo1x_1_compress( m_pData, i32DataSize, pCompressed, &ui32CompressedSize, pWorkmem ) != LZO_E_OK )
			ui32Error = IO_ERROR_WRITE;

		// write compressed data size
		ui32Error = pSave->write( &ui32CompressedSize, sizeof( ui32CompressedSize ) );

		// write compressed data
		ui32Error = pSave->write( pCompressed, ui32CompressedSize );

		delete [] pWorkmem;
		delete [] pCompressed;

	pSave->end_chunk();

	return ui32Error;
}

uint32
TGAImportC::load( LoadC* pLoad )
{
	uint32	ui32Error = IO_OK;
	char	szStr[256];
	uint8	ui8Tmp;

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

		switch( pLoad->get_chunk_id() ) {
		case CHUNK_TGAIMPORT_BASE:
			{
				if( pLoad->get_chunk_version() <= TGAIMPORT_VERSION ) {
					ui32Error = pLoad->read_str( szStr );
					m_sFileName = szStr;
				}
			}
			break;

		case CHUNK_TGAIMPORT_DATA:
			{
				if( pLoad->get_chunk_version() <= TGAIMPORT_VERSION ) {
					ui32Error = pLoad->read( &m_i32Width, sizeof( m_i32Width ) );
					ui32Error = pLoad->read( &m_i32Height, sizeof( m_i32Height ) );
					ui32Error = pLoad->read( &ui8Tmp, sizeof( ui8Tmp ) );
					m_i32Bpp = ui8Tmp;
					// delete old data if any
					delete m_pData;


					//
					// decompress
					//

					uint32	ui32DataSize = m_i32Width * m_i32Height * (m_i32Bpp / 8);
					uint32	ui32DecompressedSize;
					m_pData = new uint8[ui32DataSize];
					uint32	ui32CompressedSize;

					// read compressed data size
					ui32Error = pLoad->read( &ui32CompressedSize, sizeof( ui32CompressedSize ) );

					uint8*	pCompressed = new uint8[ui32CompressedSize];

					// write compressed data
					ui32Error = pLoad->read( pCompressed, ui32CompressedSize );

					if( lzo1x_decompress( pCompressed, ui32CompressedSize, m_pData, &ui32DecompressedSize, NULL ) != LZO_E_OK )
						ui32Error = IO_ERROR_READ;

					if( ui32DecompressedSize != ui32DataSize )
						OutputDebugString( "data size mismatch\n" );

					delete [] pCompressed;

					// The version 1 had the image updside down.
					if( pLoad->get_chunk_version() == TGAIMPORT_VERSION_1 ) {

						uint32	ui32RowSize = m_i32Width * (m_i32Bpp / 8);

						uint8*	pTmpLineTop = new uint8[ui32RowSize];
						uint8*	pTmpLineBottom = new uint8[ui32RowSize];

						for( uint32 ui32YTop = 0, ui32YBot = m_i32Height - 1; ui32YTop < ui32YBot; ui32YTop++, ui32YBot-- ) {
							memcpy( pTmpLineTop, m_pData + (ui32YTop * ui32RowSize), ui32RowSize );
							memcpy( pTmpLineBottom, m_pData + (ui32YBot * ui32RowSize), ui32RowSize );

							memcpy( m_pData + (ui32YTop * ui32RowSize), pTmpLineBottom, ui32RowSize );
							memcpy( m_pData + (ui32YBot * ui32RowSize), pTmpLineTop, ui32RowSize );
						}

						delete [] pTmpLineTop;
						delete [] pTmpLineBottom;
					}
				}
			}
			break;

		default:
			assert( 0 );
		}

		pLoad->close_chunk();

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

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

	return IO_OK;
}
*/