//
// HakftoneGridPlugin.cpp
//
// Hakftone Grid 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>

// Demopaja headers
#include "DemopajaVersion.h"
#include "PajaTypes.h"
#include "ClassDescC.h"
#include "ImportableI.h"
#include "OpenGLDeviceC.h"
#include "OpenGLViewportC.h"
#include "HalftoneGridPlugin.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 HalftoneGridPlugin;


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

HalftoneGridDescC::HalftoneGridDescC()
{
	// empty
}

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

void*
HalftoneGridDescC::create()
{
	return (void*)HalftoneGridEffectC::create_new();
}

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

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

ClassIdC
HalftoneGridDescC::get_class_id() const
{
	return CLASS_HALFTONEGRID_EFFECT;
};

const char*
HalftoneGridDescC::get_name() const
{
	return "Halftone Grid";
}

const char*
HalftoneGridDescC::get_desc() const
{
	return "Halftone Grid Effect";
}

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

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

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

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

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

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


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

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


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

HalftoneGridDescC	g_rHalftoneGridDesc;

#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_rHalftoneGridDesc;
	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 "HalftoneGridPlugin.dll - Halftone Grid Effect plugin (c)2000 memon/moppi productions";
}

#endif



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


#define		MAX_SIZE		50

HalftoneGridEffectC::HalftoneGridEffectC()
{
	//
	// 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(	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 );

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

	// render mode
	ParamIntC*	pRenderMode = ParamIntC::create_new( m_pAttGizmo, "Render Mode", 0, ID_ATTRIBUTE_RENDERMODE, PARAM_STYLE_COMBOBOX, PARAM_ANIMATABLE, 0, 2 );
	pRenderMode->add_label( 0, "Normal" );
	pRenderMode->add_label( 1, "Add" );
	pRenderMode->add_label( 2, "Mult" );
	m_pAttGizmo->add_parameter( pRenderMode );

	// color source
	ParamIntC*	pColorSource = ParamIntC::create_new( m_pAttGizmo, "Color Source", 0, ID_ATTRIBUTE_COLOR_SOURCE, PARAM_STYLE_COMBOBOX, PARAM_ANIMATABLE, 0, 1 );
	pColorSource->add_label( 0, "Constant" );
	pColorSource->add_label( 1, "Scroll Image" );
	m_pAttGizmo->add_parameter( pColorSource );

	// size source
	ParamIntC*	pSizeSource = ParamIntC::create_new( m_pAttGizmo, "Size source", 4, ID_ATTRIBUTE_SIZE_SOURCE, PARAM_STYLE_COMBOBOX, PARAM_ANIMATABLE, 0, 4 );
	pSizeSource->add_label( 0, "None" );
	pSizeSource->add_label( 1, "Scroll R" );
	pSizeSource->add_label( 2, "Scroll G" );
	pSizeSource->add_label( 3, "Scroll B" );
	pSizeSource->add_label( 4, "Scroll A" );
	m_pAttGizmo->add_parameter( pSizeSource );

	// Mask source
	ParamIntC*	pMaskSource = ParamIntC::create_new( m_pAttGizmo, "Mask source", 4, ID_ATTRIBUTE_MASK_SOURCE, PARAM_STYLE_COMBOBOX, PARAM_ANIMATABLE, 0, 3 );
	pMaskSource->add_label( 0, "Mask R" );
	pMaskSource->add_label( 1, "Mask G" );
	pMaskSource->add_label( 2, "Mask B" );
	pMaskSource->add_label( 3, "Mask A" );
	m_pAttGizmo->add_parameter( pMaskSource );

	// Scroll speed
	m_pAttGizmo->add_parameter( ParamFloatC::create_new( m_pAttGizmo, "Scroll Speed", 10.0f, ID_ATTRIBUTE_SCROLL_SPEED, PARAM_STYLE_EDITBOX, PARAM_ANIMATABLE, 0, 100, 1.0f ) );

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

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

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

	
	//
	// Control Points gizmo
	//

	m_pPointsGizmo = AutoGizmoC::create_new( this, "Grid", ID_GIZMO_POINTS );

	// width
	m_pPointsGizmo->add_parameter( ParamIntC::create_new( m_pPointsGizmo, "Width", 20, ID_POINTS_WIDTH, PARAM_STYLE_EDITBOX, PARAM_NOT_ANIMATABLE, 2, MAX_SIZE ) );

	// height
	m_pPointsGizmo->add_parameter( ParamIntC::create_new( m_pPointsGizmo, "Height", 10, ID_POINTS_HEIGHT, PARAM_STYLE_EDITBOX, PARAM_NOT_ANIMATABLE, 2, MAX_SIZE ) );


	m_pPointsGizmo->add_parameter(	ParamVector2C::create_new( m_pPointsGizmo, "Anchor Start", Vector2C( -50, 0 ), ID_POINTS_ANCHOR_START,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_REL_POSITION | PARAM_STYLE_OBJECT_SPACE, PARAM_ANIMATABLE ) );
	
	m_pPointsGizmo->add_parameter(	ParamVector2C::create_new( m_pPointsGizmo, "Control Start", Vector2C( -25, 0 ), ID_POINTS_CONTROL_START,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_REL_POSITION | PARAM_STYLE_OBJECT_SPACE, PARAM_ANIMATABLE ) );
	
	m_pPointsGizmo->add_parameter(	ParamVector2C::create_new( m_pPointsGizmo, "Control End", Vector2C( 25, 0 ), ID_POINTS_CONTROL_END,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_REL_POSITION | PARAM_STYLE_OBJECT_SPACE, PARAM_ANIMATABLE ) );
	
	m_pPointsGizmo->add_parameter(	ParamVector2C::create_new( m_pPointsGizmo, "Anchor End", Vector2C( 50, 0 ), ID_POINTS_ANCHOR_END,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_REL_POSITION | PARAM_STYLE_OBJECT_SPACE, PARAM_ANIMATABLE ) );
	
	m_pPointsGizmo->add_parameter(	ParamFloatC::create_new( m_pPointsGizmo, "Size Envelope", 15, ID_POINTS_SIZE_ENVELOPE,
						PARAM_STYLE_EDITBOX, PARAM_ANIMATABLE, 0, 0, 0.1f ) );

	m_pPointsGizmo->add_parameter(	ParamFloatC::create_new( m_pPointsGizmo, "Column Offset", 0, ID_POINTS_COLUMN_OFFSET,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_PERCENT, PARAM_ANIMATABLE, -0.1f, 0.1f, 0.01f ) );
	// grid visibility
	ParamIntC*	pShowGrid = ParamIntC::create_new( m_pPointsGizmo, "Grid", 1, ID_POINTS_SHOW_GRID, PARAM_STYLE_COMBOBOX, PARAM_NOT_ANIMATABLE, 0, 1 );
	pShowGrid->add_label( 0, "Show" );
	pShowGrid->add_label( 1, "Hide" );
	m_pPointsGizmo->add_parameter( pShowGrid );


}

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

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

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

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

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

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

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

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

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

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

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

GizmoI*
HalftoneGridEffectC::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_POINTS:
		return m_pPointsGizmo;
	}

	return 0;
}

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

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

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

ParamI*
HalftoneGridEffectC::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
HalftoneGridEffectC::initialize( uint32 ui32Reason, DemoInterfaceC* pInterface )
{
	EffectI::initialize( ui32Reason, pInterface );
}


void
CalcBezierCurve( const Vector2C& rAnc0, const Vector2C& rCtl0,
				 const Vector2C& rCtl1, const Vector2C& rAnc1,
				 int32 i32Count, Vector2C* pOut, float32 f32Offset )
{
	float32	dt;
	float32	t, t_2, t_3;
	float32	t1, t1_2, t1_3;

	dt = 1 / (float)(i32Count - 1);
	t = f32Offset;

	for( int32 i = 0; i < i32Count; i++ ) {  
		t_2 = t * t;
		t_3 = t_2 * t;
		t1 = 1.0f - t;
		t1_2 = t1 * t1;
		t1_3 = t1_2 * t1;

		pOut[i][0] = (t1_3 * rAnc0[0]) + (3.0f * t * t1_2 * rCtl0[0]) + (3.0f * t_2 * t1 * rCtl1[0]) + t_3 * rAnc1[0];
		pOut[i][1] = (t1_3 * rAnc0[1]) + (3.0f * t * t1_2 * rCtl0[1]) + (3.0f * t_2 * t1 * rCtl1[1]) + t_3 * rAnc1[1];

		t += dt;
	}
}


static
float32
eval_scale( uint32 x, uint32 y, float32 f32Offset, uint32 ui32Width, uint32 ui32Height, uint32 ui32BPP, uint8* pData, uint32 ch )
{
	if( !pData || ui32Width < 1 || ui32Height < 1 || ui32BPP < 1 )
		return 1.0f;

	int32	i32A = (int32)(f32Offset * 256.0f) & 255;
	uint32	ui32Offset = (uint32)f32Offset;

	y %= ui32Height;
	y = (int32)ui32Height - 1 - (int32)y;

	uint32	ui32Idx1 = (((x + ui32Offset) % ui32Width) + (y * ui32Width)) * (ui32BPP / 8) + ch;
	uint32	ui32Idx2 = (((x + ui32Offset + 1) % ui32Width) + (y * ui32Width)) * (ui32BPP / 8) + ch;

	return (float32)((pData[ui32Idx1] * (255 - i32A)) + pData[ui32Idx2] * i32A) / 65536.0f;
}


static
ColorC
eval_color( uint32 x, uint32 y, float32 f32Offset, uint32 ui32Width, uint32 ui32Height, uint32 ui32BPP, uint8* pData )
{
	if( !pData || ui32Width < 1 || ui32Height < 1 || ui32BPP < 1 )
		return 1.0f;

	int32	i32A = (int32)(f32Offset * 256.0f) & 255;
	uint32	ui32Offset = (uint32)f32Offset;

	y %= ui32Height;
	y = (int32)ui32Height - 1 - (int32)y;

	uint32	ui32Idx1 = (((x + ui32Offset) % ui32Width) + (y * ui32Width)) * (ui32BPP / 8);
	uint32	ui32Idx2 = (((x + ui32Offset + 1) % ui32Width) + (y * ui32Width)) * (ui32BPP / 8);

	float32	f32R = 1;
	float32	f32G = 1;
	float32	f32B = 1;
	float32	f32A = 1;

	if( ui32BPP == 8 ) {
		f32A = (float32)((pData[ui32Idx1] * (255 - i32A)) + pData[ui32Idx2] * i32A) / 65536.0f;
	}
	else if( ui32BPP == 24 ) {
		f32R = (float32)((pData[ui32Idx1 + 0] * (255 - i32A)) + pData[ui32Idx2 + 0] * i32A) / 65536.0f;
		f32G = (float32)((pData[ui32Idx1 + 1] * (255 - i32A)) + pData[ui32Idx2 + 1] * i32A) / 65536.0f;
		f32B = (float32)((pData[ui32Idx1 + 2] * (255 - i32A)) + pData[ui32Idx2 + 2] * i32A) / 65536.0f;
	}
	else if( ui32BPP == 32 ) {
		f32R = (float32)((pData[ui32Idx1 + 0] * (255 - i32A)) + pData[ui32Idx2 + 0] * i32A) / 65536.0f;
		f32G = (float32)((pData[ui32Idx1 + 1] * (255 - i32A)) + pData[ui32Idx2 + 1] * i32A) / 65536.0f;
		f32B = (float32)((pData[ui32Idx1 + 2] * (255 - i32A)) + pData[ui32Idx2 + 2] * i32A) / 65536.0f;
		f32A = (float32)((pData[ui32Idx1 + 3] * (255 - i32A)) + pData[ui32Idx2 + 3] * i32A) / 65536.0f;
	}

	return ColorC( f32R, f32G, f32B, f32A );
}


static
float32
eval_mask_scale( uint32 x, uint32 y, uint32 ui32Width, uint32 ui32Height, uint32 ui32BPP, uint8* pData, uint32 ch )
{
	if( !pData || ui32Width < 1 || ui32Height < 1 || ui32BPP < 1 )
		return 1.0f;

	uint32	ui32Idx1 = ((x % ui32Width) + ((y % ui32Height) * ui32Width)) * (ui32BPP / 8) + ch;

	return (float32)pData[ui32Idx1] / 255.0f;
}


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

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

	int32		i, j;
	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;

	// get anchor points
	((ParamVector2C*)m_pPointsGizmo->get_parameter( ID_POINTS_ANCHOR_START ))->get_val( i32Time, m_rAnchorStart );
	((ParamVector2C*)m_pPointsGizmo->get_parameter( ID_POINTS_CONTROL_START ))->get_val( i32Time, m_rControlStart );
	((ParamVector2C*)m_pPointsGizmo->get_parameter( ID_POINTS_CONTROL_END ))->get_val( i32Time, m_rControlEnd );
	((ParamVector2C*)m_pPointsGizmo->get_parameter( ID_POINTS_ANCHOR_END ))->get_val( i32Time, m_rAnchorEnd );
	((ParamIntC*)m_pPointsGizmo->get_parameter( ID_POINTS_SHOW_GRID ))->get_val( i32Time, m_i32ShowGrid );

	// get grid size
	((ParamIntC*)m_pPointsGizmo->get_parameter( ID_POINTS_WIDTH ))->get_val( i32Time, m_i32GridWidth );
	((ParamIntC*)m_pPointsGizmo->get_parameter( ID_POINTS_HEIGHT ))->get_val( i32Time, m_i32GridHeight );

	// get rendering params
	((ParamIntC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_RENDERMODE ))->get_val( i32Time, m_i32RenderMode );
	((ParamColorC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_COLOR ))->get_val( i32Time, m_rFillColor );
	((ParamIntC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_COLOR_SOURCE ))->get_val( i32Time, m_i32ColorSource );
	((ParamIntC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_SIZE_SOURCE ))->get_val( i32Time, m_i32SizeSource );
	((ParamIntC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_MASK_SOURCE ))->get_val( i32Time, m_i32MaskSource );


	float32	f32Speed;
	((ParamFloatC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_SCROLL_SPEED ))->get_val( i32Time, f32Speed );
	m_f32ScrollOffset = (float32)i32Time / 256.0f * (f32Speed / 25.0f);

	int32	i32MinTime;
	int32	i32MaxTime;
	int32	i32DeltaTime;

	// Calc size env
	ParamFloatC*	pSizeParam = (ParamFloatC*)m_pPointsGizmo->get_parameter( ID_POINTS_SIZE_ENVELOPE );
	ControllerC*	pContSize = pSizeParam->get_controller();

	i32MinTime = pContSize->get_min_time();
	i32MaxTime = pContSize->get_max_time();
	i32DeltaTime = i32MaxTime - i32MinTime;

	if( m_rSize.size() != m_i32GridWidth )
		m_rSize.resize( m_i32GridWidth );

	if( pContSize && pContSize->get_key_count() > 1 && i32DeltaTime > 0 ) {
		float32	f32Size;

		for( i = 0; i < m_i32GridWidth; i++ ) {
			int32 i32T = i32MinTime + (i32DeltaTime * i) / (float32)(m_i32GridWidth);
			pContSize->get_value( f32Size, i32T );
			m_rSize[i] = f32Size;
		}
	}
	else {
		float32	f32Size;
		pSizeParam->get_val( i32Time, f32Size );
		for( i = 0; i < m_i32GridWidth; i++ )
			m_rSize[i] = f32Size;
	}

	// Calc column offset
	ParamFloatC*	pColOffsetParam = (ParamFloatC*)m_pPointsGizmo->get_parameter( ID_POINTS_COLUMN_OFFSET );
	ControllerC*	pContOffset = pColOffsetParam->get_controller();

	i32MinTime = pContOffset->get_min_time();
	i32MaxTime = pContOffset->get_max_time();
	i32DeltaTime = i32MaxTime - i32MinTime;

	if( m_rColOffset.size() != m_i32GridHeight )
		m_rColOffset.resize( m_i32GridHeight );

	if( pContOffset && pContOffset->get_key_count() > 1 && i32DeltaTime > 0 ) {
		float32	f32Offset;

		for( i = 0; i < m_i32GridHeight; i++ ) {
			int32 i32T = i32MinTime + (i32DeltaTime * i) / (float32)(m_i32GridHeight - 1);
			pContOffset->get_value( f32Offset, i32T );
			m_rColOffset[i] = f32Offset;
		}
	}
	else {
		float32	f32Offset;
		pColOffsetParam->get_val( i32Time, f32Offset );
		for( i = 0; i < m_i32GridHeight; i++ )
			m_rColOffset[i] = f32Offset;
	}

	if( m_rGrid.size() != (m_i32GridWidth * m_i32GridHeight) )
		m_rGrid.resize( m_i32GridWidth * m_i32GridHeight );


	Vector2C	rSpine[MAX_SIZE];
	Vector2C	rDelta, rBone;

	// make grid
	for( j = 0; j < m_i32GridHeight; j++ ) {

		// Calc spine
		CalcBezierCurve( m_rAnchorStart, m_rControlStart,
						 m_rControlEnd, m_rAnchorEnd,
						 m_i32GridWidth, rSpine, m_rColOffset[j] );

		for( i = 0; i < m_i32GridWidth; i++ ) {
			if( (i + 1) >= m_i32GridWidth ) {
				rDelta = rSpine[i] - rSpine[i - 1];
			}
			else {
				rDelta = rSpine[i + 1] - rSpine[i];
			}

			rDelta = rDelta.normalize();

			rBone[0] = rDelta[1];
			rBone[1] = -rDelta[0];

			float32	f32V = ((float32)j / (float32)(m_i32GridHeight - 1)) * 2.0f - 1.0f;
			m_rGrid[i + j * m_i32GridWidth] = rSpine[i] + rBone * f32V * m_rSize[i];
		}
	}



	Vector2C	rMin, rMax;

	rMin = rMax = m_rGrid[0];

	for( i = 1; i < m_rGrid.size(); i++ ) {
		rMin[0] = __min( rMin[0], m_rGrid[i][0] );
		rMin[1] = __min( rMin[1], m_rGrid[i][1] );
		rMax[0] = __max( rMax[0], m_rGrid[i][0] );
		rMax[1] = __max( rMax[1], m_rGrid[i][1] );
	}

	m_rMin = rMin;
	m_rMax = rMax;

	rMin *= m_rTM;
	rMax *= m_rTM;

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


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


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



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


	if( m_i32RenderMode == RENDERMODE_NORMAL ) {
		// Normal
		glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
	}
	else if( m_i32RenderMode == RENDERMODE_ADD ) {
		// Add
		glBlendFunc( GL_SRC_ALPHA, GL_ONE );
	}
	else {
		// Mult
		glBlendFunc( GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA );
	}

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




	// Scroll
	uint8*				pScrollData = 0;
	uint32				ui32ScrollWidth = 0, ui32ScrollHeight = 0;
	uint32				ui32ScrollBPP = 0;

	ImportableImageI*	pImpScroll = 0;
	FileHandleC*		pScrollHandle;
	int32				i32ScrollFileTime;
	((ParamFileC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_SCROLL_IMAGE ))->get_file( i32Time, pScrollHandle, i32ScrollFileTime );

	if( pScrollHandle )
		pImpScroll = (ImportableImageI*)pScrollHandle->get_importable();
	
	if( pImpScroll ) {
		pImpScroll->eval_state( i32ScrollFileTime );
		ui32ScrollWidth = pImpScroll->get_data_pitch();
		ui32ScrollHeight = pImpScroll->get_data_height();
		ui32ScrollBPP = pImpScroll->get_data_bpp();
		pScrollData = pImpScroll->get_data();
	}

	// Mask
	uint8*				pMaskData = 0;
	uint32				ui32MaskWidth = 0, ui32MaskHeight = 0;
	uint32				ui32MaskBPP = 0;

	ImportableImageI*	pImpMask = 0;
	int32				i32MaskFileTime;
	FileHandleC*		pMaskHandle;
	((ParamFileC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_MASK_IMAGE ))->get_file( i32Time, pMaskHandle, i32MaskFileTime );
	if( pMaskHandle )
		pImpMask = (ImportableImageI*)pMaskHandle->get_importable();
	
	if( pImpMask ) {
		pImpMask->eval_state( i32MaskFileTime );
		ui32MaskWidth = pImpMask->get_data_pitch();
		ui32MaskHeight = pImpMask->get_data_height();
		ui32MaskBPP = pImpMask->get_data_bpp();
		pMaskData = pImpMask->get_data();
	}


	// dot
	ImportableImageI*	pImpImage = 0;
	int32				i32ImageFileTime;
	FileHandleC*		pImageHandle;
	((ParamFileC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_DOT_IMAGE ))->get_file( i32Time, pImageHandle, i32ImageFileTime );
	if( pImageHandle )
		pImpImage = (ImportableImageI*)pImageHandle->get_importable();
	
	if( pImpImage ) {
		// use image
		pImpImage->eval_state( i32ImageFileTime );
		pImpImage->bind_texture( pDevice, 0, IMAGE_CLAMP | IMAGE_LINEAR );
		glEnable( GL_TEXTURE_2D );
	}
	else
		glDisable( GL_TEXTURE_2D );



	int32	i32ScrollDataOffset = 0;
	if( ui32ScrollBPP >= 24 ) {
		if( m_i32SizeSource == SIZE_SOURCE_R )
			i32ScrollDataOffset = 0;
		else if( m_i32SizeSource == SIZE_SOURCE_G )
			i32ScrollDataOffset = 1;
		else if( m_i32SizeSource == SIZE_SOURCE_B )
			i32ScrollDataOffset = 2;
	}
	else if( ui32ScrollBPP >= 32 ) {
		if( m_i32SizeSource == SIZE_SOURCE_A )
			i32ScrollDataOffset = 3;
	}
	else {
		i32ScrollDataOffset = 0;
	}

	int32	i32MaskDataOffset = 0;
	if( ui32MaskBPP >= 24 ) {
		if( m_i32MaskSource == MASK_SOURCE_R )
			i32MaskDataOffset = 0;
		else if( m_i32MaskSource == MASK_SOURCE_G )
			i32MaskDataOffset = 1;
		else if( m_i32MaskSource == MASK_SOURCE_B )
			i32MaskDataOffset = 2;
	}
	else if( ui32MaskBPP >= 32 ) {
		if( m_i32MaskSource == MASK_SOURCE_A )
			i32MaskDataOffset = 3;
	}
	else {
		i32MaskDataOffset = 0;
	}

	glBegin( GL_QUADS );

	for( i = 0; i < (m_i32GridHeight - 1); i++ ) {
		for( j = 0; j < (m_i32GridWidth - 1); j++ ) {

			Vector2C	rV[4], rCenter;
			float32		f32Scale = eval_mask_scale( j, i, ui32MaskWidth, ui32MaskHeight, ui32MaskBPP, pMaskData, i32MaskDataOffset );

			if( m_i32SizeSource != SIZE_SOURCE_NONE )
				f32Scale *= eval_scale( j, i, m_f32ScrollOffset, ui32ScrollWidth, ui32ScrollHeight, ui32ScrollBPP, pScrollData, i32ScrollDataOffset );

			if( m_i32ColorSource == COLOR_SOURCE_SCROLL ) {
				ColorC	rCol = eval_color( j, i, m_f32ScrollOffset, ui32ScrollWidth, ui32ScrollHeight, ui32ScrollBPP, pScrollData );
				if( m_i32RenderMode == RENDERMODE_MULT ) // mult
					glColor4f( rCol[0] * rCol[3], rCol[1] * rCol[3], rCol[2] * rCol[3], rCol[3] );
				else
					glColor4fv( rCol );
			}

			if( f32Scale < 0.001 )
				continue;

			rV[0] = m_rGrid[i * m_i32GridWidth + j];
			rV[1] = m_rGrid[i * m_i32GridWidth + j + 1];
			rV[2] = m_rGrid[(i + 1) * m_i32GridWidth + j + 1];
			rV[3] = m_rGrid[(i + 1) * m_i32GridWidth + j];

			rCenter = (rV[0] + rV[1] + rV[2] + rV[3]) / 4.0f;
			for( uint32 k = 0; k < 4; k++ )
				rV[k] = ((rV[k] - rCenter) * f32Scale) + rCenter;


			glTexCoord2f( 0, 0 );
			glVertex2f( rV[0][0], rV[0][1] );

			glTexCoord2f( 1, 0 );
			glVertex2f( rV[1][0], rV[1][1] );

			glTexCoord2f( 1, 1 );
			glVertex2f( rV[2][0], rV[2][1] );
			
			glTexCoord2f( 0, 1 );
			glVertex2f( rV[3][0], rV[3][1] );
		}
	}

	glEnd();

	glDisable( GL_TEXTURE_2D );


	// draw grid

	if( m_i32ShowGrid == 0 ) {

		glColor3ub( 255, 255, 255 );

		// rows
		for( i = 0; i < m_i32GridHeight; i++ ) {
			glBegin( GL_LINE_STRIP );
			for( uint32 j = 0; j < m_i32GridWidth; j++ )
				glVertex2f( m_rGrid[i * m_i32GridWidth + j][0], m_rGrid[i * m_i32GridWidth + j][1] );
			glEnd();
		}

		// colums
		for( i = 0; i < m_i32GridWidth; i++ ) {
			glBegin( GL_LINE_STRIP );
			for( uint32 j = 0; j < m_i32GridHeight; j++ )
				glVertex2f( m_rGrid[j * m_i32GridWidth + i][0], m_rGrid[j * m_i32GridWidth + i][1] );
			glEnd();
		}

		glColor3ub( 255, 255, 0 );
		// Draw handles
		glBegin( GL_LINES );
		// start
		glVertex2f( m_rAnchorStart[0], m_rAnchorStart[1] );
		glVertex2f( m_rControlStart[0], m_rControlStart[1] );
		// end
		glVertex2f( m_rControlEnd[0], m_rControlEnd[1] );
		glVertex2f( m_rAnchorEnd[0], m_rAnchorEnd[1] );
		glEnd();
	}

	glDepthMask( GL_TRUE );
}

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

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

bool
HalftoneGridEffectC::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;*/

	return m_rBBox.contains( rPoint );
}


enum HalftoneGridEffectChunksE {
	CHUNK_HALFTONEGRID_BASE =			0x1000,
	CHUNK_HALFTONEGRID_TRANSGIZMO =		0x2000,
	CHUNK_HALFTONEGRID_ATTRIBGIZMO =	0x3000,
	CHUNK_HALFTONEGRID_POINTSGIZMO =	0x4000,
};

const uint32	HALFTONEGRID_VERSION = 1;

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

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

	// Transform
	pSave->begin_chunk( CHUNK_HALFTONEGRID_TRANSGIZMO, HALFTONEGRID_VERSION );
		ui32Error = m_pTraGizmo->save( pSave );
	pSave->end_chunk();

	// Attribute
	pSave->begin_chunk( CHUNK_HALFTONEGRID_ATTRIBGIZMO, HALFTONEGRID_VERSION );
		ui32Error = m_pAttGizmo->save( pSave );
	pSave->end_chunk();

	// Control points
	pSave->begin_chunk( CHUNK_HALFTONEGRID_POINTSGIZMO, HALFTONEGRID_VERSION );
		ui32Error = m_pPointsGizmo->save( pSave );
	pSave->end_chunk();

	return ui32Error;
}

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

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

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

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

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

		case CHUNK_HALFTONEGRID_POINTSGIZMO:
			// Attribute
			if( pLoad->get_chunk_version() == HALFTONEGRID_VERSION )
				ui32Error = m_pPointsGizmo->load( pLoad );
			break;

		default:
			assert( 0 );
		}

		pLoad->close_chunk();

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

	return ui32Error;
}
