//
// BlurPlugin.cpp
//
// Blur Plugin
//
// Copyright (c) 2000 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 <stdio.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 "BlurPlugin.h"
#include "FileIO.h"
#include "AutoGizmoC.h"
#include "ImportableImageI.h"

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

using namespace BlurPlugin;



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



//////////////////////////////////////////////////////////////////////////
//
//  Blur effect class descriptor.
//

BlurDescC::BlurDescC()
{
	// empty
}

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

void*
BlurDescC::create()
{
	return (void*)BlurEffectC::create_new();
}

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

SuperClassIdC
BlurDescC::get_super_class_id() const
{
	return SUPERCLASS_EFFECT_POST_PROCESS;
}

ClassIdC
BlurDescC::get_class_id() const
{
	return CLASS_BLUR_EFFECT;
};

const char*
BlurDescC::get_name() const
{
	return "Blur";
}

const char*
BlurDescC::get_desc() const
{
	return "Blur Effect";
}

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

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

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

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

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

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


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

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


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

BlurDescC	g_rBlurDesc;

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


//
// Returns number of classes inside this plugin DLL.
//

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


//
// Returns class descriptors of the plugin classes.
//

__declspec( dllexport )
ClassDescC*
get_classdesc( int32 i )
{
	if( i == 0 )
		return &g_rBlurDesc;
	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 "BlurPlugin.dll - Blur Effect plugin (c)2000 memon/moppi productions";
}

#endif



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

BlurEffectC::BlurEffectC() :
	m_pGBuffer( 0 )
{
	//
	// Create Attributes gizmo.
	//
	m_pAttGizmo = AutoGizmoC::create_new( this, "Attributes", ID_GIZMO_ATTRIB );

	// Blur Amount
	m_pAttGizmo->add_parameter(	ParamFloatC::create_new( m_pAttGizmo, "Size", 1, ID_ATTRIBUTE_SIZE,
						PARAM_STYLE_EDITBOX, PARAM_ANIMATABLE, 0, 32, 0.1f ) );

	// Blend type
	ParamIntC*	pBlendMode = ParamIntC::create_new( m_pAttGizmo, "Blend Mode", 0, ID_ATTRIBUTE_BLEND_MODE, PARAM_STYLE_COMBOBOX, PARAM_ANIMATABLE, 0, 2 );
	pBlendMode->add_label( 0, "Normal" );
	pBlendMode->add_label( 1, "Add" );
	pBlendMode->add_label( 2, "Mult" );
	m_pAttGizmo->add_parameter( pBlendMode );

	// Blur blend amount
	m_pAttGizmo->add_parameter(	ParamFloatC::create_new( m_pAttGizmo, "Blend Amount", 0.0f, ID_ATTRIBUTE_BLEND_AMOUNT,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_PERCENT, PARAM_ANIMATABLE, 0.0f, 1.0f, 0.01f ) );
}

BlurEffectC::BlurEffectC( EditableI* pOriginal ) :
	EffectPostProcessI( pOriginal ),
	m_pAttGizmo( 0 ),
	m_pGBuffer( 0 )
{
	// Empty. The parameters are not created in the clone constructor.
}

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

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

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

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

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

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

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

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

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

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

GizmoI*
BlurEffectC::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_ATTRIB:
		return m_pAttGizmo;
	}

	return 0;
}

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

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

void
BlurEffectC::set_default_file( int32 i32Time, FileHandleC* pHandle )
{
	// empty
}

ParamI*
BlurEffectC::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_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;
}

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

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

/*	if( ui32Reason == INIT_DEVICE_CHANGED ) {

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

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

	}*/
}


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


#define		MAX_PASSES	31

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

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

	m_rBBox[0] = Vector2C( 0, 0 );
	m_rBBox[1] = Vector2C( 0, 0 );
	m_rTM.set_identity();

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

	GraphicsBufferI*	pGBuffer = pDevice->get_temp_graphicsbuffer( 0 );
	if( !pGBuffer )
		return;

	BBox2C	rLayout = pViewport->get_layout();

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

	BBox2C	rTexBounds = pGBuffer->get_tex_coord_bounds();

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

	pGBuffer->bind_texture( pDevice, 0, IMAGE_NEAREST | IMAGE_CLAMP );

	// draw old texture blended
	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );

	glColor4ub( 255, 255, 255, 255 );

	glBegin( GL_QUADS );

		glTexCoord2f( rTexBounds[0][0], rTexBounds[0][1] );
		glVertex2f( rLayout[0][0], rLayout[0][1] );

		glTexCoord2f( rTexBounds[1][0], rTexBounds[0][1] );
		glVertex2f( rLayout[1][0], rLayout[0][1] );
		
		glTexCoord2f( rTexBounds[1][0], rTexBounds[1][1] );
		glVertex2f( rLayout[1][0], rLayout[1][1] );
		
		glTexCoord2f( rTexBounds[0][0], rTexBounds[1][1] );
		glVertex2f( rLayout[0][0], rLayout[1][1] );

	glEnd();

	glDisable( GL_TEXTURE_2D );
	glEnable( GL_DEPTH_TEST );
	glDisable( GL_BLEND );

	glDepthMask( GL_TRUE );

/*
	int32		i;
	Matrix2C	rPosMat, rRotMat, rScaleMat, rPivotMat;

	Vector2C	rScale;
	Vector2C	rPos;
	Vector2C	rPivot;

	// 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 );
	((ParamVector2C*)m_pTraGizmo->get_parameter( ID_TRANSFORM_SCALE ))->get_val( i32Time, rScale );

	// Calculate transformation matrix.
	rPivotMat.set_trans( rPivot );
	rPosMat.set_trans( rPos );
	rScaleMat.set_scale( rScale ) ;
	m_rTM = rPivotMat * rScaleMat * rPosMat;

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

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

	PajaTypes::float32	f32Amount = 1.0f;
	PajaTypes::int32	i32Direction = 0;
	PajaTypes::float32	f32Feedback = 0;

	// Get Amount
	((ParamFloatC*)m_pAttGizmo->get_parameter_by_id( ID_ATTRIBUTE_SIZE ))->get_val( i32Time, f32Amount );

	// Get Direction
	((ParamIntC*)m_pAttGizmo->get_parameter_by_id( ID_ATTRIBUTE_DIRECTION ))->get_val( i32Time, i32Direction );

	// Get Feedback
	((ParamFloatC*)m_pAttGizmo->get_parameter_by_id( ID_ATTRIBUTE_FEEDBACK ))->get_val( i32Time, f32Feedback );


	//
	// draw effect
	//

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

	if( ceil( f32Amount ) < 1 )
		return;


	// Set orthographic projection.
	pViewport->set_ortho_pixel( m_rBBox );

	// get image from frame buffer
	int32	i32Width = pViewport->get_width();
	int32	i32Height = pViewport->get_height();

	// If the maximum image size has changed, invalidate texture object if necessary
	if( ceil_power2( i32Width ) != m_ui32TexWidth || ceil_power2( i32Height ) != m_ui32TexHeight ) {
		if( m_ui32TexId )
			glDeleteTextures( 1, &m_ui32TexId );
		m_ui32TexId = 0;
	}

	if( !m_ui32TexId ) {
		glGenTextures( 1, &m_ui32TexId );
		glBindTexture( GL_TEXTURE_2D, m_ui32TexId );
		m_ui32TexWidth = ceil_power2( i32Width );
		m_ui32TexHeight = ceil_power2( i32Height );
		glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, m_ui32TexWidth, m_ui32TexHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, 0 );
	}
	else {
		glBindTexture( GL_TEXTURE_2D, m_ui32TexId );
	}

	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 );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );


	// calc where we get our image
	Vector2C	rOffset( (MAX_PASSES + 1) / 2 + 1, (MAX_PASSES + 1) / 2 + 1 );
	BBox2C	rCapture;
	rCapture[0] = pViewport->layout_to_client( m_rBBox[0] - rOffset );
	rCapture[1] = pViewport->layout_to_client( m_rBBox[1] + rOffset );

	int32	i32X, i32Y;

	i32X = (int32)floor( rCapture[0][0] );
	i32Y = (int32)floor( rCapture[0][1] );

	m_i32TexRealWidth = (int32)ceil( rCapture.width() );
	m_i32TexRealHeight = (int32)ceil( rCapture.height() );

	if( i32X >= i32Width )
		return;
	if( i32X < 0 ) {
		m_i32TexRealWidth += i32X;
		i32X = 0;
	}
	if( (i32X + m_i32TexRealWidth) >= i32Width )
		m_i32TexRealWidth -= (i32X + m_i32TexRealWidth) - i32Width;
	if( m_i32TexRealWidth <= 0 )
		return;

	if( i32Y >= i32Height )
		return;
	if( i32Y < 0 ) {
		m_i32TexRealHeight += i32Y;
		i32Y = 0;
	}
	if( (i32Y + m_i32TexRealHeight) >= i32Height )
		m_i32TexRealHeight -= (i32Y + m_i32TexRealHeight) - i32Height;
	if( m_i32TexRealHeight <= 0 )
		return;



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

	if( m_bValidFeedback && f32Feedback > 0.01f ) {

		// draw old texture blended
		glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );

		glColor4ub( 255, 255, 255, (GLubyte)(f32Feedback * 255.0f) );

		glBegin( GL_QUADS );

			glTexCoord2f( m_f32LastTexX0, m_f32LastTexY0 );
			glVertex2f( (float32)m_i32LastX, (float32)m_i32LastY );

			glTexCoord2f( m_f32LastTexX1, m_f32LastTexY0 );
			glVertex2f( (float32)(m_i32LastX + m_i32LastWidth), (float32)m_i32LastY );
			
			glTexCoord2f( m_f32LastTexX1, m_f32LastTexY1 );
			glVertex2f( (float32)(m_i32LastX + m_i32LastWidth), (float32)(m_i32LastY + m_i32LastHeight) );
			
			glTexCoord2f( m_f32LastTexX0, m_f32LastTexY1 );
			glVertex2f( (float32)m_i32LastX, (float32)(m_i32LastY + m_i32LastHeight) );

		glEnd();
	}




	if( i32Direction == 2 )
		glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE );
	else
		glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_FALSE );

	int32	i32W = ceil_power2( m_i32TexRealWidth );
	int32	i32H = ceil_power2( m_i32TexRealHeight );
	i32W = __min( i32W, (int32)m_ui32TexWidth );
	i32H = __min( i32H, (int32)m_ui32TexHeight );

	glCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, i32X, i32Y, i32W, i32H );

//	glCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, i32X, i32Y, m_i32TexRealWidth, m_i32TexRealHeight );

	// draw blur

	float32	f32TexX0 = 0;
	float32	f32TexY0 = 0;
	float32	f32TexX1 = (float32)m_i32TexRealWidth;
	float32	f32TexY1 = (float32)m_i32TexRealHeight;

	float32	f32InvTexWidth = 1.0f/ (float32)(m_ui32TexWidth);
	float32	f32InvTexHeight = 1.0f/ (float32)(m_ui32TexHeight);

	f32TexX0 *= f32InvTexWidth;
	f32TexY0 *= f32InvTexHeight;
	f32TexX1 *= f32InvTexWidth;
	f32TexY1 *= f32InvTexHeight;

	Vector2C	rOne( 1, 1 );
	rOne = pViewport->delta_layout_to_client( rOne );


	if( i32Direction == 2 ) {
		// "gaussian" blur
		// clear area
		glBlendFunc( GL_ZERO, GL_ZERO );
		glBegin( GL_QUADS );
		glVertex2f( i32X, i32Y );
		glVertex2f( i32X + m_i32TexRealWidth, i32Y );
		glVertex2f( i32X + m_i32TexRealWidth, i32Y + m_i32TexRealHeight );
		glVertex2f( i32X, i32Y + m_i32TexRealHeight );
		glEnd();


		glHint( GL_GENERATE_MIPMAP_HINT_SGIS, GL_FASTEST );

		GLfloat	fOldBias;
		glGetTexEnvfv( GL_TEXTURE_FILTER_CONTROL_EXT, GL_TEXTURE_LOD_BIAS_EXT, &fOldBias );


		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR );

//		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR );
//		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST_MIPMAP_LINEAR );

		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );

		// Draw blur

//		glBlendFunc( GL_ONE, GL_ZERO );
		glBlendFunc( GL_ONE, GL_ONE );

		glColor3ub( 64, 64, 64 );


		float	fOffset = f32Amount * 0.3f;

		float32	f32LodBias = (float32)(log( f32Amount * rOne[0] ) / log( 2.0f ));

		float	fOffX = f32InvTexWidth * fOffset;
		float	fOffY = f32InvTexHeight * fOffset;

		glTexEnvf( GL_TEXTURE_FILTER_CONTROL_EXT, GL_TEXTURE_LOD_BIAS_EXT, f32LodBias );


		glBegin( GL_QUADS );

			// 1
			glTexCoord2f( f32TexX0 - fOffX, f32TexY0 );
			glVertex2f( (float32)i32X, (float32)i32Y );

			glTexCoord2f( f32TexX1 - fOffX, f32TexY0 );
			glVertex2f( (float32)(i32X + m_i32TexRealWidth), (float32)i32Y );
			
			glTexCoord2f( f32TexX1 - fOffX, f32TexY1 );
			glVertex2f( (float32)(i32X + m_i32TexRealWidth), (float32)(i32Y + m_i32TexRealHeight) );
			
			glTexCoord2f( f32TexX0 - fOffX, f32TexY1 );
			glVertex2f( (float32)i32X, (float32)(i32Y + m_i32TexRealHeight) );

		glEnd();

//		glTexEnvf( GL_TEXTURE_FILTER_CONTROL_EXT, GL_TEXTURE_LOD_BIAS_EXT, f32Amount - 0.3f );

		glBegin( GL_QUADS );

			// 2
			glTexCoord2f( f32TexX0 + fOffX, f32TexY0 );
			glVertex2f( (float32)i32X, (float32)i32Y );

			glTexCoord2f( f32TexX1 + fOffX, f32TexY0 );
			glVertex2f( (float32)(i32X + m_i32TexRealWidth), (float32)i32Y );
			
			glTexCoord2f( f32TexX1 + fOffX, f32TexY1 );
			glVertex2f( (float32)(i32X + m_i32TexRealWidth), (float32)(i32Y + m_i32TexRealHeight) );
			
			glTexCoord2f( f32TexX0 + fOffX, f32TexY1 );
			glVertex2f( (float32)i32X, (float32)(i32Y + m_i32TexRealHeight) );

		glEnd();

//		glTexEnvf( GL_TEXTURE_FILTER_CONTROL_EXT, GL_TEXTURE_LOD_BIAS_EXT, f32Amount - 0.6f );

		glBegin( GL_QUADS );

			// 3
			glTexCoord2f( f32TexX0, f32TexY0 - fOffY );
			glVertex2f( (float32)i32X, (float32)i32Y );

			glTexCoord2f( f32TexX1, f32TexY0 - fOffY );
			glVertex2f( (float32)(i32X + m_i32TexRealWidth), (float32)i32Y );
			
			glTexCoord2f( f32TexX1, f32TexY1 - fOffY );
			glVertex2f( (float32)(i32X + m_i32TexRealWidth), (float32)(i32Y + m_i32TexRealHeight) );
			
			glTexCoord2f( f32TexX0, f32TexY1 - fOffY );
			glVertex2f( (float32)i32X, (float32)(i32Y + m_i32TexRealHeight) );

		glEnd();

//		glTexEnvf( GL_TEXTURE_FILTER_CONTROL_EXT, GL_TEXTURE_LOD_BIAS_EXT, f32Amount - 0.9f );

		glBegin( GL_QUADS );

			// 4
			glTexCoord2f( f32TexX0, f32TexY0 + fOffY );
			glVertex2f( (float32)i32X, (float32)i32Y );

			glTexCoord2f( f32TexX1, f32TexY0 + fOffY );
			glVertex2f( (float32)(i32X + m_i32TexRealWidth), (float32)i32Y );
			
			glTexCoord2f( f32TexX1, f32TexY1 + fOffY );
			glVertex2f( (float32)(i32X + m_i32TexRealWidth), (float32)(i32Y + m_i32TexRealHeight) );
			
			glTexCoord2f( f32TexX0, f32TexY1 + fOffY );
			glVertex2f( (float32)i32X, (float32)(i32Y + m_i32TexRealHeight) );

		glEnd();

		glTexEnvf( GL_TEXTURE_FILTER_CONTROL_EXT, GL_TEXTURE_LOD_BIAS_EXT, fOldBias );

	}
	else {
		// Horiz/vert blur
		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 );
		glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );

		// clear area
		glBlendFunc( GL_ZERO, GL_ZERO );
		glBegin( GL_QUADS );
		glVertex2i( i32X, i32Y );
		glVertex2i( i32X + m_i32TexRealWidth, i32Y );
		glVertex2i( i32X + m_i32TexRealWidth, i32Y + m_i32TexRealHeight );
		glVertex2i( i32X, i32Y + m_i32TexRealHeight );
		glEnd();


		// Draw blur

		glBlendFunc( GL_ONE, GL_ONE );


		int32	i32Passes = (int32)ceil( f32Amount );

		if( i32Passes < 2 )
			i32Passes = 2;

		if( i32Passes > 15 )
			i32Passes = 15;

		float32	f32GeomIncX = 0;
		float32	f32GeomOffsetX = 0;
		float32	f32GeomIncY = 0;
		float32	f32GeomOffsetY = 0;

		if( i32Direction == 0 ) {
			f32GeomIncX = (f32Amount - 1) / (float32)i32Passes * rOne[0];
			f32GeomOffsetX = -(f32Amount - 1) / 2.0f * rOne[0];
		}
		else {
			f32GeomIncY = (f32Amount - 1) / (float32)i32Passes * rOne[0];
			f32GeomOffsetY = -(f32Amount - 1) / 2.0f * rOne[0];
		}

		int32	i32Alpha = 255 / i32Passes;

		glBegin( GL_QUADS );

		for( i = 0; i < i32Passes; i++ ) {

			if( !i && i32Passes > 1 ) {
				int32	i32LeftOver = 255 - (i32Passes - 1) * i32Alpha;
				glColor4ub( i32LeftOver, i32LeftOver, i32LeftOver, i32LeftOver );
			}
			else
				glColor4ub( i32Alpha, i32Alpha, i32Alpha, i32Alpha );

			glTexCoord2f( f32TexX0, f32TexY0 );
			glVertex2f( (float32)i32X + f32GeomOffsetX, (float32)i32Y + f32GeomOffsetY );

			glTexCoord2f( f32TexX1, f32TexY0 );
			glVertex2f( (float32)(i32X + m_i32TexRealWidth) + f32GeomOffsetX, (float32)i32Y + f32GeomOffsetY );
			
			glTexCoord2f( f32TexX1, f32TexY1 );
			glVertex2f( (float32)(i32X + m_i32TexRealWidth) + f32GeomOffsetX, (float32)(i32Y + m_i32TexRealHeight) + f32GeomOffsetY );
			
			glTexCoord2f( f32TexX0, f32TexY1 );
			glVertex2f( (float32)i32X + f32GeomOffsetX, (float32)(i32Y + m_i32TexRealHeight) + f32GeomOffsetY );

			f32GeomOffsetX += f32GeomIncX;
			f32GeomOffsetY += f32GeomIncY;
		}

		glEnd();
	}

	// save for feedback
	m_f32LastTexX0 = f32TexX0;
	m_f32LastTexY0 = f32TexY0;
	m_f32LastTexX1 = f32TexX1;
	m_f32LastTexY1 = f32TexY1;

	m_i32LastX = i32X;
	m_i32LastY = i32Y;
	m_i32LastWidth = m_i32TexRealWidth;
	m_i32LastHeight = m_i32TexRealHeight;

	// mark that the frame is captured
	m_bValidFeedback = true;

	glDisable( GL_TEXTURE_2D );
	glEnable( GL_DEPTH_TEST );
	glDisable( GL_BLEND );

	glDepthMask( GL_TRUE );
*/
}

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

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

bool
BlurEffectC::hit_test( const Vector2C& rPoint )
{
	return false;
/*
	// 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;*/
}

GraphicsBufferI*
BlurEffectC::get_render_target()
{
	DeviceContextC* pContext = m_pDemoInterface->get_device_context();
	if( !pContext )
		return 0;

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

	return pDevice->get_temp_graphicsbuffer( 0 );
}


enum BlurEffectChunksE {
	CHUNK_BLUR_BASE =			0x1000,
	CHUNK_BLUR_TRANSGIZMO =		0x2000,
	CHUNK_BLUR_ATTRIBGIZMO =	0x3000,
};

const uint32	BLUR_VERSION = 1;

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

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

	// Transform
/*	pSave->begin_chunk( CHUNK_BLUR_TRANSGIZMO, BLUR_VERSION );
		ui32Error = m_pTraGizmo->save( pSave );
	pSave->end_chunk();*/

	// Attribute
	pSave->begin_chunk( CHUNK_BLUR_ATTRIBGIZMO, BLUR_VERSION );
		ui32Error = m_pAttGizmo->save( pSave );
	pSave->end_chunk();

	return ui32Error;
}

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

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

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

		case CHUNK_BLUR_TRANSGIZMO:
/*			// Transform
			if( pLoad->get_chunk_version() == BLUR_VERSION )
				ui32Error = m_pTraGizmo->load( pLoad );*/
			break;

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

		default:
			assert( 0 );
		}

		pLoad->close_chunk();

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

	return ui32Error;
}
