//
// FluidPlugin.cpp
//
// Fluid 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 "FluidPlugin.h"
#include "FileIO.h"
#include "AutoGizmoC.h"
#include "ImportableImageI.h"

#include <rfftw.h>

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

using namespace FluidPlugin;


//
// random
//
static	uint32	g_ui32Seed = 1;

inline
void
set_rnd_seed( PajaTypes::uint32 ui32Seed )
{
	g_ui32Seed = ui32Seed;
}

inline
uint32
irnd()
{
	return (uint32)(g_ui32Seed = 0x015a4e35 * g_ui32Seed + 1) >> 16;
}


inline
float32
frnd()
{
	return (float32)((uint32)(g_ui32Seed = 0x015a4e35 * g_ui32Seed + 1) >> 16) * 2.0f * (1.0f / 65536.0f) - 1.0f;
}




//////////////////////////////////////////////////////////////////////////
//
//  Fluid effect class descriptor.
//

FluidDescC::FluidDescC()
{
	// empty
}

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

void*
FluidDescC::create()
{
	return (void*)FluidEffectC::create_new();
}

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

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

ClassIdC
FluidDescC::get_class_id() const
{
	return CLASS_FLUID_EFFECT;
};

const char*
FluidDescC::get_name() const
{
	return "Fluid";
}

const char*
FluidDescC::get_desc() const
{
	return "Fluid Effect";
}

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

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

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

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

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

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


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

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


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

FluidDescC	g_rFluidDesc;

#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_rFluidDesc;
	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 "FluidPlugin.dll - Fluid plugin (c)2000 memon/moppi productions";
}

#endif



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

FluidEffectC::FluidEffectC() :
	m_i32ValidTime( 0 ),
	m_bInvalidate( 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(	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 );

	// Mix mode mode
	ParamIntC*	pMixMode = ParamIntC::create_new( m_pAttGizmo, "Mix mode", 0, ID_ATTRIBUTE_MIXMODE, PARAM_STYLE_COMBOBOX, PARAM_ANIMATABLE, 0, 1 );
	pMixMode->add_label( 0, "Reveal" );
	pMixMode->add_label( 1, "Hide" );
	m_pAttGizmo->add_parameter( pMixMode );

	// Chaos
	m_pAttGizmo->add_parameter(	ParamFloatC::create_new( m_pAttGizmo, "Chaos", 0, ID_ATTRIBUTE_CHAOS,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_PERCENT, PARAM_ANIMATABLE, 0, 1, 0.01f ) );

	// Deform
	m_pAttGizmo->add_parameter(	ParamFloatC::create_new( m_pAttGizmo, "Deform Image", 0, ID_ATTRIBUTE_DEFORM,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_PERCENT, PARAM_ANIMATABLE, 0, 1, 0.01f ) );

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


	//
	// emitters
	//

	m_pEm1Gizmo = AutoGizmoC::create_new( this, "Emitter1", ID_GIZMO_EMITTER1 );
	fill_emitter_gizmo( m_pEm1Gizmo, true );
	
	m_pEm2Gizmo = AutoGizmoC::create_new( this, "Emitter2", ID_GIZMO_EMITTER2 );
	fill_emitter_gizmo( m_pEm2Gizmo, false );
	
	m_pEm3Gizmo = AutoGizmoC::create_new( this, "Emitter3", ID_GIZMO_EMITTER3 );
	fill_emitter_gizmo( m_pEm3Gizmo, false );
	
	m_pEm4Gizmo = AutoGizmoC::create_new( this, "Emitter4", ID_GIZMO_EMITTER4 );
	fill_emitter_gizmo( m_pEm4Gizmo, false );
	
	m_pEm5Gizmo = AutoGizmoC::create_new( this, "Emitter5", ID_GIZMO_EMITTER5 );
	fill_emitter_gizmo( m_pEm5Gizmo, false );
	
}


void
FluidEffectC::fill_emitter_gizmo( AutoGizmoC* pGizmo, bool bOn )
{
	// Emitter mode
	ParamIntC*	pEmitterMode = ParamIntC::create_new( m_pAttGizmo, "Emitter mode", 0, ID_EMITTER_MODE, PARAM_STYLE_COMBOBOX, PARAM_ANIMATABLE, 0, 2 );
	pEmitterMode->add_label( 0, "Off" );
	pEmitterMode->add_label( 1, "Material" );
	pEmitterMode->add_label( 2, "Obstacle" );
	if( bOn )
		pEmitterMode->set_val( 0, 1 );
	pGizmo->add_parameter( pEmitterMode );

	// Position
	pGizmo->add_parameter(	ParamVector2C::create_new( m_pAttGizmo, "Position", Vector2C( 0.5f, 0.5f ), ID_EMITTER_POSITION,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_REL_POSITION | PARAM_STYLE_OBJECT_SPACE, PARAM_ANIMATABLE ) );

	// Rotation
	pGizmo->add_parameter(	ParamFloatC::create_new( m_pAttGizmo, "Rotation", 0, ID_EMITTER_ROTATION,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_ANGLE, PARAM_ANIMATABLE, 0, 0, 1.0f ) );

	// Amount
	pGizmo->add_parameter(	ParamFloatC::create_new( m_pAttGizmo, "Strength", 0.5f, ID_EMITTER_AMOUNT,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_PERCENT, PARAM_ANIMATABLE, 0, 1.0f, 0.01f ) );
}


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

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

	// Release gizmos.
	m_pTraGizmo->release();
	m_pAttGizmo->release();
	m_pEm1Gizmo->release();
	m_pEm2Gizmo->release();
	m_pEm3Gizmo->release();
	m_pEm4Gizmo->release();
	m_pEm5Gizmo->release();
}

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

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

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

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

	// Make deep copy.
	FluidEffectC*	pEffect = (FluidEffectC*)pEditable;
	m_pTraGizmo->copy( pEffect->m_pTraGizmo );
	m_pAttGizmo->copy( pEffect->m_pAttGizmo );
	m_pEm1Gizmo->copy( pEffect->m_pEm1Gizmo );
	m_pEm2Gizmo->copy( pEffect->m_pEm2Gizmo );
	m_pEm3Gizmo->copy( pEffect->m_pEm3Gizmo );
	m_pEm4Gizmo->copy( pEffect->m_pEm4Gizmo );
	m_pEm5Gizmo->copy( pEffect->m_pEm5Gizmo );
}

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

	// Make shallow copy.
	FluidEffectC*	pEffect = (FluidEffectC*)pEditable;
	m_pTraGizmo = pEffect->m_pTraGizmo;
	m_pAttGizmo = pEffect->m_pAttGizmo;
	m_pEm1Gizmo = pEffect->m_pEm1Gizmo;
	m_pEm2Gizmo = pEffect->m_pEm2Gizmo;
	m_pEm3Gizmo = pEffect->m_pEm3Gizmo;
	m_pEm4Gizmo = pEffect->m_pEm4Gizmo;
	m_pEm5Gizmo = pEffect->m_pEm5Gizmo;
}

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

GizmoI*
FluidEffectC::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_EMITTER1:
		return m_pEm1Gizmo;
	case ID_GIZMO_EMITTER2:
		return m_pEm2Gizmo;
	case ID_GIZMO_EMITTER3:
		return m_pEm3Gizmo;
	case ID_GIZMO_EMITTER4:
		return m_pEm4Gizmo;
	case ID_GIZMO_EMITTER5:
		return m_pEm5Gizmo;
	}

	return 0;
}

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

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

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

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

	if( ui32Reason == INIT_INITIAL_UPDATE )
	{
		m_rFluid.init( 64 );
	}
}


void
FluidEffectC::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;


	// Calculate bounding box vertices

	// Initial values
	float32		f32Width = 25;
	float32		f32Height = 25;

	// If image is used, get the initial dimension from it.
	ImportableImageI*	pImpImage = 0;
	FileHandleC*		pImageHandle;
	int32				i32FileTime;
	((ParamFileC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_FILE ))->get_file( i32Time, pImageHandle, i32FileTime );
	if( pImageHandle )
		pImpImage = (ImportableImageI*)pImageHandle->get_importable();
	
	if( pImpImage ) {
		pImpImage->eval_state( i32FileTime );
		f32Width = pImpImage->get_width() * 0.5f;
		f32Height = pImpImage->get_height() * 0.5f;
	}

	// Calcualte vertices of the bounding box.
	Vector2C	rMin, rMax;
	Vector2C	rVec;

	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 from vertices
	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;

	ColorC	rFillColor;
	int32	i32RenderMode = 0;
	int32	i32MixMode = 0;
	float32	f32Chaos = 0;
	float32	f32DeformScale = 0;

	// Get fill color.
	((ParamColorC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_COLOR ))->get_val( i32Time, rFillColor );

	// Get rendermode
	((ParamIntC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_RENDERMODE ))->get_val( i32Time, i32RenderMode );

	// Get mixmode
	((ParamIntC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_MIXMODE ))->get_val( i32Time, i32MixMode );

	// Get chaos
	((ParamFloatC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_CHAOS ))->get_val( i32Time, f32Chaos );

	// Get deform scale
	((ParamFloatC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_DEFORM ))->get_val( i32Time, f32DeformScale );

	f32DeformScale *= 10.0f;

	const uint32	ui32EmitterCount = 5;
	EmitterS	rEmitters[ui32EmitterCount];

	for( i = 0; i < ui32EmitterCount; i++ ) {
		AutoGizmoC*	pGiz;
		if( i == 0 )
			pGiz = m_pEm1Gizmo;
		else if( i == 1 )
			pGiz = m_pEm2Gizmo;
		else if( i == 2 )
			pGiz = m_pEm3Gizmo;
		else if( i == 3 )
			pGiz = m_pEm4Gizmo;
		else if( i == 4 )
			pGiz = m_pEm5Gizmo;
		else
			continue;

		// mode
		((ParamIntC*)pGiz->get_parameter( ID_EMITTER_MODE ))->get_val( i32Time, rEmitters[i].m_i32Mode );
		// pos
		((ParamVector2C*)pGiz->get_parameter( ID_EMITTER_POSITION ))->get_val( i32Time, rEmitters[i].m_rPos );
		// rot
		((ParamFloatC*)pGiz->get_parameter( ID_EMITTER_ROTATION ))->get_val( i32Time, rEmitters[i].m_f32Rot );
		// amount
		((ParamFloatC*)pGiz->get_parameter( ID_EMITTER_AMOUNT ))->get_val( i32Time, rEmitters[i].m_f32Amount );
	}


	//
	// render 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_rBBox[0][0], m_rBBox[1][0], m_rBBox[1][1], m_rBBox[0][1] );

	glMatrixMode( GL_MODELVIEW );


	// If an image is provided, use it instead of capturing the screen.
	if( pImpImage ) {
		pImpImage->bind_texture( pDevice, 0, IMAGE_CLAMP | IMAGE_LINEAR );
		glEnable( GL_TEXTURE_2D );
	}
	else {
		glDisable( GL_TEXTURE_2D );
	}

	// apply force

	int32	i32FrameSizeInTicks = 256 / pTimeContext->get_edit_accuracy();

	if( i32Time < m_i32ValidTime || m_bInvalidate ) {
		m_i32ValidTime = 0;
		m_rFluid.reset();
	}

	while( m_i32ValidTime < i32Time  ) {

		// Get warp offset
//		static Vector2C	rPrevOffset( 0, 0 );
//		Vector2C	rOffset;
//		((ParamVector2C*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_OFFSET ))->get_val( m_i32ValidTime, rOffset );

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

			if( rEmitters[i].m_i32Mode == EMITTER_NONE )
				continue;

			// Add force at the cursor location
			Vector2C	rPos = rEmitters[i].m_rPos;
			float32	f32Rot = rEmitters[i].m_f32Rot / 180.0f * M_PI;
			Vector2C	rDelta( cos( f32Rot ), sin( f32Rot ) );
			rPos += rDelta;
			rDelta *= 0.07f + (rEmitters[i].m_f32Amount) * 0.05f;

			float32	f32X, f32Y;
			f32X = rEmitters[i].m_rPos[0] / (f32Width * 2.0f) + 0.5f;
			f32Y = rEmitters[i].m_rPos[1] / (f32Height * 2.0f) + 0.5f;

			if( rEmitters[i].m_i32Mode == EMITTER_MATERIAL ) {
				m_rFluid.drag( f32X, f32Y, rDelta, rEmitters[i].m_f32Amount * 20.0f );
			}
			else if( rEmitters[i].m_i32Mode == EMITTER_OBSTACLE ) {
				m_rFluid.push( f32X, f32Y, rDelta, rEmitters[i].m_f32Amount * 3.0f );
			}

		}

		int32	i32FrameSize = i32FrameSizeInTicks;
		float32	f32DT = 1.0f;

		if( (i32Time - m_i32ValidTime) < i32FrameSizeInTicks ) {
			i32FrameSize = i32Time - m_i32ValidTime;
			f32DT = (float32)i32FrameSize / (float32)i32FrameSizeInTicks;
		}


		set_rnd_seed( (uint32)i32Time );

		// add chaos
		if( f32Chaos > 0 ) {
			for( i = 0; i < m_rFluid.get_dimension() * 2; i++ ) {
				uint32	ui32X = irnd() % m_rFluid.get_dimension();
				uint32	ui32Y = irnd() % m_rFluid.get_dimension();
				Vector2C	rForce( frnd(), frnd() );
				m_rFluid.add_force( ui32X, ui32Y, f32Chaos * 0.1f * rForce.normalize() );
			}
		}

		// solve
		m_rFluid.solve( f32DT );

		m_i32ValidTime += i32FrameSize;
	}


	m_rFluid.smooth();

	// draw

	int32		i32Dim = m_rFluid.get_dimension();
	fftw_real*	pRho = m_rFluid.get_smooth_density_ptr();
	fftw_real*	pU = m_rFluid.get_velocity_u_ptr();
	fftw_real*	pV = m_rFluid.get_velocity_v_ptr();

	int32	i32Idx;
	float32	f32DeltaX = (fftw_real)m_rBBox.width() / (float32)(i32Dim - 1);   /* Grid element width */
	float32	f32DeltaY = (fftw_real)m_rBBox.height() / (float32)(i32Dim - 1);  /* Grid element height */
	float32	f32PX, f32PY;
	float32	f32TU, f32TV;


	// set rendering states
	glDisable( GL_DEPTH_TEST );
	glDepthMask( GL_FALSE );
	glEnable( GL_BLEND );

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

	ColorC	rCol;

/*	if( i32RenderMode == RENDERMODE_MULT ) {
		rCol[0] *= rFillColor[3];
		rCol[1] *= rFillColor[3];
		rCol[2] *= rFillColor[3];
	}*/


	for( j = 0; j < i32Dim - 1; j++ ) {

		glBegin( GL_TRIANGLE_STRIP );

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

			f32PX = m_rBBox[0][0] + (fftw_real)i * f32DeltaX;
			f32PY = m_rBBox[0][1] + (fftw_real)j * f32DeltaY;
			i32Idx = (j * i32Dim) + i;

			f32TU = (float32)i / (float32)(i32Dim - 1) + pU[i32Idx] * f32DeformScale;
			f32TV = (float32)j / (float32)(i32Dim - 1) + pV[i32Idx] * f32DeformScale;

			glTexCoord2f( f32TU, f32TV );

			rCol = rFillColor;
			if( i32MixMode == MIXMODE_REVEAL )
				rCol[3] = rFillColor[3] * pRho[i32Idx];
			else
				rCol[3] = rFillColor[3] * (1.0f - pRho[i32Idx]);

			if( i32RenderMode == RENDERMODE_MULT ) {
				rCol[0] *= rFillColor[3];
				rCol[1] *= rFillColor[3];
				rCol[2] *= rFillColor[3];
			}
			glColor4fv( rCol );

			glVertex2f( f32PX, f32PY );


			f32PX = m_rBBox[0][0] + (fftw_real)i * f32DeltaX;
			f32PY = m_rBBox[0][1] + (fftw_real)(j + 1) * f32DeltaY;
			i32Idx = (j + 1) * i32Dim + i;

			f32TU = (float32)i / (float32)(i32Dim - 1) + pU[i32Idx] * f32DeformScale;
			f32TV = (float32)(j + 1) / (float32)(i32Dim - 1) + pV[i32Idx] * f32DeformScale;

			glTexCoord2f( f32TU, f32TV );

			rCol = rFillColor;
			if( i32MixMode == MIXMODE_REVEAL )
				rCol[3] = rFillColor[3] * pRho[i32Idx];
			else
				rCol[3] = rFillColor[3] * (1.0f - pRho[i32Idx]);

			if( i32RenderMode == RENDERMODE_MULT ) {
				rCol[0] *= rFillColor[3];
				rCol[1] *= rFillColor[3];
				rCol[2] *= rFillColor[3];
			}
			glColor4fv( rCol );

			glVertex2f( f32PX, f32PY );
		}

		glEnd();
	}


	glDepthMask( GL_TRUE );
	glDisable( GL_TEXTURE_2D );
}

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

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

bool
FluidEffectC::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 FluidEffectChunksE {
	CHUNK_WARP_BASE =			0x1000,
	CHUNK_WARP_TRANSGIZMO =		0x2000,
	CHUNK_WARP_ATTRIBGIZMO =	0x3000,
	CHUNK_WARP_EMITTER1 =		0x4001,
	CHUNK_WARP_EMITTER2 =		0x4002,
	CHUNK_WARP_EMITTER3 =		0x4003,
	CHUNK_WARP_EMITTER4 =		0x4004,
	CHUNK_WARP_EMITTER5 =		0x4005,
};

const uint32	WARP_VERSION = 1;

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

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

	// Transform
	pSave->begin_chunk( CHUNK_WARP_TRANSGIZMO, WARP_VERSION );
		ui32Error = m_pTraGizmo->save( pSave );
	pSave->end_chunk();

	// Attribute
	pSave->begin_chunk( CHUNK_WARP_ATTRIBGIZMO, WARP_VERSION );
		ui32Error = m_pAttGizmo->save( pSave );
	pSave->end_chunk();

	// Amitter1
	pSave->begin_chunk( CHUNK_WARP_EMITTER1, WARP_VERSION );
		ui32Error = m_pEm1Gizmo->save( pSave );
	pSave->end_chunk();

	// Amitter2
	pSave->begin_chunk( CHUNK_WARP_EMITTER2, WARP_VERSION );
		ui32Error = m_pEm2Gizmo->save( pSave );
	pSave->end_chunk();

	// Amitter3
	pSave->begin_chunk( CHUNK_WARP_EMITTER3, WARP_VERSION );
		ui32Error = m_pEm3Gizmo->save( pSave );
	pSave->end_chunk();

	// Amitter4
	pSave->begin_chunk( CHUNK_WARP_EMITTER4, WARP_VERSION );
		ui32Error = m_pEm4Gizmo->save( pSave );
	pSave->end_chunk();

	// Amitter5
	pSave->begin_chunk( CHUNK_WARP_EMITTER5, WARP_VERSION );
		ui32Error = m_pEm5Gizmo->save( pSave );
	pSave->end_chunk();

	return ui32Error;
}

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

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

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

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

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

		case CHUNK_WARP_EMITTER1:
			// Em1
			if( pLoad->get_chunk_version() == WARP_VERSION )
				ui32Error = m_pEm1Gizmo->load( pLoad );
			break;

		case CHUNK_WARP_EMITTER2:
			// Em1
			if( pLoad->get_chunk_version() == WARP_VERSION )
				ui32Error = m_pEm2Gizmo->load( pLoad );
			break;

		case CHUNK_WARP_EMITTER3:
			// Em1
			if( pLoad->get_chunk_version() == WARP_VERSION )
				ui32Error = m_pEm3Gizmo->load( pLoad );
			break;

		case CHUNK_WARP_EMITTER4:
			// Em1
			if( pLoad->get_chunk_version() == WARP_VERSION )
				ui32Error = m_pEm4Gizmo->load( pLoad );
			break;

		case CHUNK_WARP_EMITTER5:
			// Em1
			if( pLoad->get_chunk_version() == WARP_VERSION )
				ui32Error = m_pEm5Gizmo->load( pLoad );
			break;

		default:
			assert( 0 );
		}

		pLoad->close_chunk();

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

	return ui32Error;
}
