//
// GradientEffect.cpp
//
// Gradient Effect
//
// 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 "StylizePlugin.h"
#include "FileIO.h"
#include "AutoGizmoC.h"
#include "ImportableImageI.h"
#include "ImageResampleC.h"
#include "EffectMaskI.h"

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

using namespace GradientPlugin;




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


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

static bool							g_bTextureShader = false;


void
init_gl_extensions()
{
	char*	szExtensions;	
	szExtensions = (char*)glGetString( GL_EXTENSIONS );

	if( !g_bMultiTexture ) {

		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 ) {
		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" );
		}
	}

	// Check for texture shader
	if( !g_bTextureShader )
	{
		if( szExtensions && strstr( szExtensions, "GL_NV_texture_shader" ) != 0 )
		{
			TRACE( "has texture shader\n" );
			g_bTextureShader = true;
		}
	}
}


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


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

GradientDescC::GradientDescC()
{
	// empty
}

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

void*
GradientDescC::create()
{
	return (void*)GradientEffectC::create_new();
}

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

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

ClassIdC
GradientDescC::get_class_id() const
{
	return CLASS_GRADIENT_EFFECT;
};

const char*
GradientDescC::get_name() const
{
	return "Stylize Gradient";
}

const char*
GradientDescC::get_desc() const
{
	return "Stylize Gradient Effect";
}

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

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

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

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

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

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


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

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


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

GradientDescC	g_rGradientDesc;

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


uint32	GradientEffectC::m_ui32WhiteTextureID = 0;
uint32	GradientEffectC::m_ui32BlackTextureID = 0;
uint32	GradientEffectC::m_ui32GradientTextureID = 0;
int32		GradientEffectC::m_i32TextureRefCount = 0;


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

	// Weigth
	m_pAttGizmo->add_parameter(	ParamVector3C::create_new( m_pAttGizmo, "Weight", Vector3C( 0.3f, 0.3f, 0.3f ), ID_ATTRIBUTE_WEIGHT,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_PERCENT, PARAM_ANIMATABLE, Vector3C( 0, 0, 0 ), Vector3C( 1, 1, 1 ), 0.01f ) );

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


	//
	// Gradient gizmo
	//
	//

	m_pGradGizmo = AutoGizmoC::create_new( this, "Gradient", ID_GIZMO_GRADIENT );

	// Color A
	m_pGradGizmo->add_parameter(	ParamColorC::create_new( m_pGradGizmo, "Color A", ColorC( 0, 0, 0, 1 ), ID_GRADIENT_COLOR_A,
							PARAM_STYLE_COLORPICKER_RGB, PARAM_ANIMATABLE ) );

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

	// Color B
	m_pGradGizmo->add_parameter(	ParamColorC::create_new( m_pGradGizmo, "Color B", ColorC( 1, 1, 1, 1 ), ID_GRADIENT_COLOR_B,
							PARAM_STYLE_COLORPICKER_RGB, PARAM_ANIMATABLE ) );

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

	// Midpoint
	m_pGradGizmo->add_parameter(	ParamFloatC::create_new( m_pGradGizmo, "Midpoint", 0.5f, ID_GRADIENT_MIDPOINT,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_PERCENT, PARAM_ANIMATABLE, 0, 1.0f, 0.01f ) );

	// Steepness
	m_pGradGizmo->add_parameter(	ParamFloatC::create_new( m_pGradGizmo, "Steepness", 0.0f, ID_GRADIENT_STEEPNESS,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_PERCENT, PARAM_ANIMATABLE, 0, 1.0f, 0.01f ) );


}

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

GradientEffectC::~GradientEffectC()
{
	// 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();
	m_pGradGizmo->release();
}

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

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

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

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

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

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

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

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

GizmoI*
GradientEffectC::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;
	case ID_GIZMO_GRADIENT:
		return m_pGradGizmo;
	}

	return 0;
}

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

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

void
GradientEffectC::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*
GradientEffectC::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
GradientEffectC::update_notify( EditableI* pCaller )
{
	return PARAM_NOTIFY_NONE;
}

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

	init_gl_extensions();

	init_textures();
}

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

	// Requires multitexturing and bledn equation.
	if( !g_bMultiTexture || !g_bBlendEqu || !g_bTextureShader )
	{
		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;
	}
	else
	{
		// No image, no image...
		return;
	}


	// 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;
	Vector3C	rWeight;
	ColorC		rColorA( 1, 1, 1 );
	float32		f32OpacityA = 1.0f;
	ColorC		rColorB( 1, 1, 1 );
	float32		f32OpacityB = 1.0f;
	float32		f32MidPoint = 0.5f;
	float32		f32Steepness = 0.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 Resample
	((ParamVector3C*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_WEIGHT ))->get_val( i32Time, rWeight );

	// Get Color.
	((ParamColorC*)m_pGradGizmo->get_parameter( ID_GRADIENT_COLOR_A ))->get_val( i32Time, rColorA );
	// Get Opacity.
	((ParamFloatC*)m_pGradGizmo->get_parameter( ID_GRADIENT_OPACITY_A ))->get_val( i32Time, f32OpacityA );
	// Get Color.
	((ParamColorC*)m_pGradGizmo->get_parameter( ID_GRADIENT_COLOR_B ))->get_val( i32Time, rColorB );
	// Get Opacity.
	((ParamFloatC*)m_pGradGizmo->get_parameter( ID_GRADIENT_OPACITY_B ))->get_val( i32Time, f32OpacityB );
	// Get Midpoint.
	((ParamFloatC*)m_pGradGizmo->get_parameter( ID_GRADIENT_MIDPOINT ))->get_val( i32Time, f32MidPoint );
	// Get steepness.
	((ParamFloatC*)m_pGradGizmo->get_parameter( ID_GRADIENT_STEEPNESS ))->get_val( i32Time, f32Steepness );

	rColorA[3] = f32OpacityA;
	rColorB[3] = f32OpacityB;

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


	// Set shader
	glEnable( GL_TEXTURE_SHADER_NV );


	// Get image
	glActiveTextureARB( GL_TEXTURE0_ARB );
	pImp->bind_texture( pDevice, 0, i32TextureFlags );
	glTexEnvi(GL_TEXTURE_SHADER_NV, GL_SHADER_OPERATION_NV, GL_TEXTURE_2D);
//	glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_NONE );

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

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


	// Gradient
	glActiveTextureARB( GL_TEXTURE1_ARB );
	glTexEnvi( GL_TEXTURE_SHADER_NV, GL_RGBA_UNSIGNED_DOT_PRODUCT_MAPPING_NV, GL_UNSIGNED_IDENTITY_NV );
	glTexEnvi( GL_TEXTURE_SHADER_NV, GL_SHADER_OPERATION_NV, GL_DOT_PRODUCT_NV );
	glTexEnvi( GL_TEXTURE_SHADER_NV, GL_PREVIOUS_TEXTURE_INPUT_NV, GL_TEXTURE0_ARB );
	glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_NONE );

	glActiveTextureARB( GL_TEXTURE2_ARB );
	glBindTexture( GL_TEXTURE_2D, m_ui32GradientTextureID );
	update_gradient( rColorA, rColorB, f32MidPoint, f32Steepness );
	glTexEnvi( GL_TEXTURE_SHADER_NV, GL_RGBA_UNSIGNED_DOT_PRODUCT_MAPPING_NV, GL_UNSIGNED_IDENTITY_NV );
	glTexEnvi( GL_TEXTURE_SHADER_NV, GL_SHADER_OPERATION_NV, GL_DOT_PRODUCT_TEXTURE_2D_NV );
	glTexEnvi( GL_TEXTURE_SHADER_NV, GL_PREVIOUS_TEXTURE_INPUT_NV, GL_TEXTURE0_ARB );
	glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );


	if( pMaskImp ) {
		
		glActiveTextureARB( GL_TEXTURE3_ARB );
		pMaskImp->bind_texture( pDevice, 3, i32MaskImageFlags );
		glTexEnvi(GL_TEXTURE_SHADER_NV, GL_SHADER_OPERATION_NV, 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 );
	}
	else
	{
		// disable
		glActiveTextureARB( GL_TEXTURE3_ARB );
		glTexEnvi( GL_TEXTURE_SHADER_NV, GL_SHADER_OPERATION_NV, GL_NONE );
		glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_NONE );
	}


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


	// Set color and opacity
	glColor4f( 1, 1, 1, 1 );

	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );


	float32	f32WeightSum = rWeight[0] + rWeight[1] + rWeight[2];
	if( f32WeightSum > 1.0f )
	{
		rWeight /= f32WeightSum;
	}

	glMultiTexCoord3fARB( GL_TEXTURE1_ARB, rWeight[0], rWeight[1], rWeight[2] );
	glMultiTexCoord3fARB( GL_TEXTURE2_ARB, 0, 0, 0 );

	// Draw rectangle.
	glBegin( GL_QUADS );

	glMultiTexCoord2fARB( GL_TEXTURE0_ARB, rTexCoords[0][0], rTexCoords[0][1] );
	if( pMaskImp )
		glMultiTexCoord2fARB( GL_TEXTURE3_ARB, rMaskTexCoords[0][0], rMaskTexCoords[0][1] );
	glVertex2f( m_rVertices[0][0], m_rVertices[0][1] );
	
	glMultiTexCoord2fARB( GL_TEXTURE0_ARB, rTexCoords[1][0], rTexCoords[1][1] );
	if( pMaskImp )
		glMultiTexCoord2fARB( GL_TEXTURE3_ARB, rMaskTexCoords[1][0], rMaskTexCoords[1][1] );
	glVertex2f( m_rVertices[1][0], m_rVertices[1][1] );
	
	glMultiTexCoord2fARB( GL_TEXTURE0_ARB, rTexCoords[2][0], rTexCoords[2][1] );
	if( pMaskImp )
		glMultiTexCoord2fARB( GL_TEXTURE3_ARB, rMaskTexCoords[2][0], rMaskTexCoords[2][1] );
	glVertex2f( m_rVertices[2][0], m_rVertices[2][1] );

	glMultiTexCoord2fARB( GL_TEXTURE0_ARB, rTexCoords[3][0], rTexCoords[3][1] );
	if( pMaskImp )
		glMultiTexCoord2fARB( GL_TEXTURE3_ARB, rMaskTexCoords[3][0], rMaskTexCoords[3][1] );
	glVertex2f( m_rVertices[3][0], m_rVertices[3][1] );

	glEnd();


	glDisable( GL_TEXTURE_SHADER_NV );

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


	glDepthMask( GL_TRUE );
}

void
GradientEffectC::update_gradient( const ColorC& rColA, const ColorC& rColB, float32 f32MidPoint, float32 f32Steepness )
{
		// Gradient

		uint8	ui8GradientTextureData[(128 * 4) * 4];

		float32	f32Start = f32MidPoint * f32Steepness;
		float32	f32End = f32MidPoint + (1.0f - f32MidPoint) * (1.0f - f32Steepness);

		for( int j = 0; j < 4; j++ )
		{
			for( int i = 0; i < 128; i++ )
			{
				int idx = (i + j * 128) * 4;

				float	f32U = (float)i / 127.0f;

				float	f32Alpha = 0;

				if( f32U < f32Start )
					f32Alpha = 1.0f;
				else if( f32U >= f32End )
					f32Alpha = 0.0f;
				else
				{
					f32Alpha = 1.0f - (f32U - f32Start) / (f32End - f32Start);
				}

				ColorC	rCol = rColA * f32Alpha + rColB * (1.0f - f32Alpha);

				ui8GradientTextureData[idx + 0] = (int)(rCol[0] * 255.0f);
				ui8GradientTextureData[idx + 1] = (int)(rCol[1] * 255.0f);
				ui8GradientTextureData[idx + 2] = (int)(rCol[2] * 255.0f);
				ui8GradientTextureData[idx + 3] = (int)(rCol[3] * 255.0f);
			}
		}

		glBindTexture( GL_TEXTURE_2D, m_ui32GradientTextureID );
		glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 128, 4, GL_RGBA, GL_UNSIGNED_BYTE, ui8GradientTextureData );
}

void
GradientEffectC::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 );


		// Gradient
		glGenTextures( 1, &m_ui32GradientTextureID );
		glBindTexture( GL_TEXTURE_2D, m_ui32GradientTextureID );
		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_RGBA8, 128, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL );
	}

	m_i32TextureRefCount++;

	m_bTextureRef = true;
}

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

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

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

bool
GradientEffectC::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_GRADIENT_BASE =				0x10,
	CHUNK_GRADIENT_TRANSGIZMO =	0x20,
	CHUNK_GRADIENT_ATTRIBGIZMO =	0x30,
	CHUNK_GRADIENT_GRADIENTGIZMO =	0x40,
};

const uint32	GRADIENT_VERSION = 1;

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

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

	// Transform
	pSave->begin_chunk( CHUNK_GRADIENT_TRANSGIZMO, GRADIENT_VERSION );
		ui32Error = m_pTraGizmo->save( pSave );
	pSave->end_chunk();

	// Attribute
	pSave->begin_chunk( CHUNK_GRADIENT_ATTRIBGIZMO, GRADIENT_VERSION );
		ui32Error = m_pAttGizmo->save( pSave );
	pSave->end_chunk();

	// Gradient
	pSave->begin_chunk( CHUNK_GRADIENT_GRADIENTGIZMO, GRADIENT_VERSION );
		ui32Error = m_pGradGizmo->save( pSave );
	pSave->end_chunk();

	return ui32Error;
}

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

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

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

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

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

		case CHUNK_GRADIENT_GRADIENTGIZMO:
			// Attribute
			if( pLoad->get_chunk_version() == GRADIENT_VERSION )
				ui32Error = m_pGradGizmo->load( pLoad );
			break;

		default:
			assert( 0 );
		}

		pLoad->close_chunk();

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

	return ui32Error;
}

