//
// DX9CrowdPlugin.cpp
//
// DX9Crowd Plugin
//
// Copyright (c) 2000-2004 memon/moppi productions
//

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <d3d9.h>
#include <d3dx9math.h>
//#include <dxerr9.h>

// Demopaja headers
#include "DemopajaVersion.h"
#include "PajaTypes.h"
#include "ClassDescC.h"
#include "ImportableI.h"
#include "DX9DeviceC.h"
#include "Dx9ViewportC.h"
#include "FileIO.h"
#include "AutoGizmoC.h"
#include "Vector2C.h"
#include "BBox2C.h"
#include "EffectMaskI.h"
#include "DX9MeshPlugin.h"
#include "DX9CameraPlugin.h"
#include "DX9CrowdPlugin.h"
#include "RandomMT.h"
#include "resource.h"
#include "DX9CameraPlugin.h"
#include "DX9DaylightPlugin.h"
#include "AudioSpectrumC.h"

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

using namespace DX9CrowdPlugin;
using namespace DX9MeshPlugin;
using namespace DX9CameraPlugin;
using namespace DX9DayLightPlugin;


#define FVF_XYZCOLORUV_VERTEX			(D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1)
#define FVF_XYZCOLOR_VERTEX			(D3DFVF_XYZ | D3DFVF_DIFFUSE)

struct XYZCOLORUV_VERTEX
{
	float	x, y, z;
	DWORD	color;
	float	u, v;
};

struct XYZCOLOR_VERTEX
{
	float	x, y, z;
	DWORD	color;
};


#define	SAVE_TIME_STEP	(1000 / 2)		// Save solution 2 Hz
#define TIME_STEP				(1000 / 30)		// Update 30 Hz


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

//////////////////////////////////////////////////////////////////////////
//
//  DX9Crowd effect class descriptor.
//

DX9CrowdDescC::DX9CrowdDescC()
{
	// empty
}

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

void*
DX9CrowdDescC::create()
{
	return (void*)DX9CrowdEffectC::create_new();
}

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

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

ClassIdC
DX9CrowdDescC::get_class_id() const
{
	return CLASS_DX9CROWD_EFFECT;
};

const char*
DX9CrowdDescC::get_name() const
{
	return CLASS_DX9CROWD_EFFECT_NAME;
}

const char*
DX9CrowdDescC::get_desc() const
{
	return CLASS_DX9CROWD_EFFECT_DESC;
}

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

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

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

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

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

const ClassIdC&
DX9CrowdDescC::get_required_device_driver( uint32 ui32Idx )
{
	return PajaSystem::CLASS_DX9_DEVICEDRIVER;
}


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

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


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

DX9CrowdDescC			g_rDX9CrowdDesc;

#ifndef DEMOPAJAPLAYER

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

HINSTANCE	g_hInstance = 0;

BOOL APIENTRY
DllMain( HANDLE hModule, DWORD ulReasonForCall, LPVOID lpReserved )
{
    switch( ulReasonForCall )
	{
		case DLL_PROCESS_ATTACH:
		case DLL_THREAD_ATTACH:
			g_hInstance = (HINSTANCE)hModule;
			break;
		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_rDX9CrowdDesc;
	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 "DX9CrowdPlugin.dll - Image Plugin (c)2000-2004 memon/moppi productions";
}

#endif



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

DX9CrowdEffectC::DX9CrowdEffectC() :
	m_pEffect( 0 ),
	m_pHeroInstance( 0 )
{
	//
	// 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, "Scale", Vector2C( 1, 1 ), ID_TRANSFORM_SCALE,
						PARAM_STYLE_EDITBOX | PARAM_STYLE_PERCENT, PARAM_ANIMATABLE, Vector2C(), Vector2C(), 0.01f ) );

	//
	// Create Attributes gizmo.
	//

/*
		ID_ATTRIBUTE_FILE_OBSTACLES = 0,
		ID_ATTRIBUTE_LOOK_AT_OBJECT,
		ID_ATTRIBUTE_HERO_OBJECT,
		ID_ATTRIBUTE_FILE_HERO_MESH,
		ID_ATTRIBUTE_FILE_DUDE1_MESH,
		ID_ATTRIBUTE_FILE_DUDE2_MESH,
		ID_ATTRIBUTE_FILE_DUDE3_MESH,
		ID_ATTRIBUTE_FILE_DUDE4_MESH,
		ID_ATTRIBUTE_FILE_DUDE5_MESH,
		ID_ATTRIBUTE_TEEN_COUNT,
		ID_ATTRIBUTE_ADULT_COUNT,
*/

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

	// Obstacles.
	m_pAttGizmo->add_parameter(	ParamFileC::create_new( m_pAttGizmo, "Obstacles", NULL_SUPERCLASS, CLASS_DX9ASE_IMPORT, ID_ATTRIBUTE_FILE_OBSTACLES, PARAM_STYLE_FILE, PARAM_NOT_ANIMATABLE ) );

	// Look at object
	m_pAttGizmo->add_parameter( ParamIntC::create_new( m_pAttGizmo, "Look At", 0, ID_ATTRIBUTE_LOOK_AT_OBJECT,
		PARAM_STYLE_COMBOBOX, PARAM_ANIMATABLE, 0, 1 ) );

	// Hero object
	m_pAttGizmo->add_parameter( ParamIntC::create_new( m_pAttGizmo, "Hero", 0, ID_ATTRIBUTE_HERO_OBJECT,
		PARAM_STYLE_COMBOBOX, PARAM_NOT_ANIMATABLE, 0, 1 ) );

	// Hero anim
	m_pAttGizmo->add_parameter( ParamIntC::create_new( m_pAttGizmo, "Hero Anim", 0, ID_ATTRIBUTE_HERO_ANIM,
		PARAM_STYLE_COMBOBOX, PARAM_ANIMATABLE, 0, 1 ) );

	// Hero anim time
	m_pAttGizmo->add_parameter( ParamFloatC::create_new( m_pAttGizmo, "Hero Anim Time", 0, ID_ATTRIBUTE_HERO_ANIM_TIME,
		PARAM_STYLE_EDITBOX, PARAM_ANIMATABLE, 0, 0, 0.01f ) );

	// Mesh hero
	m_pAttGizmo->add_parameter(	ParamFileC::create_new( m_pAttGizmo, "Hero Mesh", NULL_SUPERCLASS, CLASS_DX9X_IMPORT, ID_ATTRIBUTE_FILE_HERO_MESH, PARAM_STYLE_FILE, PARAM_NOT_ANIMATABLE ) );

	// Mesh dude 1
	m_pAttGizmo->add_parameter(	ParamFileC::create_new( m_pAttGizmo, "Dude 1 Mesh", NULL_SUPERCLASS, CLASS_DX9X_IMPORT, ID_ATTRIBUTE_FILE_DUDE1_MESH, PARAM_STYLE_FILE, PARAM_NOT_ANIMATABLE ) );

	// Mesh dude 2
	m_pAttGizmo->add_parameter(	ParamFileC::create_new( m_pAttGizmo, "Dude 2 Mesh", NULL_SUPERCLASS, CLASS_DX9X_IMPORT, ID_ATTRIBUTE_FILE_DUDE2_MESH, PARAM_STYLE_FILE, PARAM_NOT_ANIMATABLE ) );

	// Mesh dude 3
	m_pAttGizmo->add_parameter(	ParamFileC::create_new( m_pAttGizmo, "Dude 3 Mesh", NULL_SUPERCLASS, CLASS_DX9X_IMPORT, ID_ATTRIBUTE_FILE_DUDE3_MESH, PARAM_STYLE_FILE, PARAM_NOT_ANIMATABLE ) );

	// Mesh dude 4
	m_pAttGizmo->add_parameter(	ParamFileC::create_new( m_pAttGizmo, "Dude 4 Mesh", NULL_SUPERCLASS, CLASS_DX9X_IMPORT, ID_ATTRIBUTE_FILE_DUDE4_MESH, PARAM_STYLE_FILE, PARAM_NOT_ANIMATABLE ) );

	// Mesh dude 5
	m_pAttGizmo->add_parameter(	ParamFileC::create_new( m_pAttGizmo, "Dude 5 Mesh", NULL_SUPERCLASS, CLASS_DX9X_IMPORT, ID_ATTRIBUTE_FILE_DUDE5_MESH, PARAM_STYLE_FILE, PARAM_NOT_ANIMATABLE ) );

	// Dude count
	m_pAttGizmo->add_parameter( ParamIntC::create_new( m_pAttGizmo, "Dude Count", 20, ID_ATTRIBUTE_DUDE_COUNT,
		PARAM_STYLE_EDITBOX, PARAM_NOT_ANIMATABLE, 1, 50 ) );

	// Teen count
	m_pAttGizmo->add_parameter( ParamIntC::create_new( m_pAttGizmo, "Teen Count", 0, ID_ATTRIBUTE_TEEN_COUNT,
		PARAM_STYLE_EDITBOX, PARAM_ANIMATABLE, 0, 50 ) );

	// Adult count
	m_pAttGizmo->add_parameter( ParamIntC::create_new( m_pAttGizmo, "Adult Count", 0, ID_ATTRIBUTE_ADULT_COUNT,
		PARAM_STYLE_EDITBOX, PARAM_ANIMATABLE, 0, 50 ) );

	m_pAttGizmo->add_parameter( ParamFloatC::create_new( m_pAttGizmo, "Bass Amount", 0, ID_ATTRIBUTE_BASS_AFFECT,
		PARAM_STYLE_EDITBOX | PARAM_STYLE_PERCENT, PARAM_ANIMATABLE, 0, 1, 0.01f ) );

	//
	// Create Time gizmo.
	//
	m_pTimeGizmo = AutoGizmoC::create_new( this, "Timing", ID_GIZMO_TIME );
	
	m_pTimeGizmo->add_parameter( ParamFloatC::create_new( m_pTimeGizmo, "Frame", 0, ID_TIME_FRAME,
						PARAM_STYLE_EDITBOX, PARAM_ANIMATABLE, 0, 0, 1.0f ) );

}

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

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

	for( uint32 i = 0; i < (uint32)m_vecVehicles.size(); i++ )
		delete m_vecVehicles[i];
	m_vecVehicles.clear();

	if( m_pHeroInstance )
		m_pHeroInstance->Release();
	m_pHeroInstance = 0;

	if( m_pEffect )
		m_pEffect->Release();

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

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

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

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

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

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

	// copy vehicles.
	reset_vehicles();
	init_meshes();
}

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

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

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

GizmoI*
DX9CrowdEffectC::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_TIME:
		return m_pTimeGizmo;
	}

	return 0;
}

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

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

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

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

	// 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*
DX9CrowdEffectC::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 );
	return 0;
}

uint32
DX9CrowdEffectC::update_notify( EditableI* pCaller )
{
	ParamI*	pParam = 0;
	GizmoI*	pGizmo = 0;
	if( pCaller->get_base_class_id() == BASECLASS_PARAMETER )
		pParam = (ParamI*)pCaller;
	if( pParam )
		pGizmo = pParam->get_parent();

	if( pParam && pGizmo && pGizmo->get_id() == ID_GIZMO_ATTRIB )
	{
		if( pParam->get_id() == ID_ATTRIBUTE_FILE_DUDE1_MESH )
			init_meshes();
		else if( pParam->get_id() == ID_ATTRIBUTE_FILE_DUDE2_MESH )
			init_meshes();
		else if( pParam->get_id() == ID_ATTRIBUTE_FILE_DUDE3_MESH )
			init_meshes();
		else if( pParam->get_id() == ID_ATTRIBUTE_FILE_DUDE4_MESH )
			init_meshes();
		else if( pParam->get_id() == ID_ATTRIBUTE_FILE_DUDE5_MESH )
			init_meshes();
		else if( pParam->get_id() == ID_ATTRIBUTE_FILE_HERO_MESH )
			init_meshes();
		else if( pParam->get_id() == ID_ATTRIBUTE_FILE_OBSTACLES )
		{
			// The file has changed. Change the combos.
			ParamFileC*		pFile = (ParamFileC*)m_pAttGizmo->get_parameter_by_id( ID_ATTRIBUTE_FILE_OBSTACLES );
			ParamIntC*		pLookAtCombo = (ParamIntC*)m_pAttGizmo->get_parameter_by_id( ID_ATTRIBUTE_LOOK_AT_OBJECT );
			ParamIntC*		pHeroCombo = (ParamIntC*)m_pAttGizmo->get_parameter_by_id( ID_ATTRIBUTE_HERO_OBJECT );
			ParamFloatC*	pFrame = (ParamFloatC*)m_pTimeGizmo->get_parameter_by_id( ID_TIME_FRAME );

			UndoC*		pOldUndo;
			UndoC*		pUndo = pFile->get_undo();

			FileHandleC*		pHandle = 0;
			DX9ASEImportC*	pImp = 0;

			pHandle = pFile->get_file( 0 );
			if( pHandle )
				pImp = (DX9ASEImportC*)pHandle->get_importable();

			if( pImp )
			{
				// Update lookat labels.
				pOldUndo = pLookAtCombo->begin_editing( pUndo );
				pLookAtCombo->clear_labels();
				pLookAtCombo->add_label( 0, "<none>" );
				for( uint32 i = 0; i < pImp->get_helper_count(); i++ ) {
					HelperC*	pHelper = pImp->get_helper( i );
					if( !pHelper )
						continue;
					pLookAtCombo->add_label( i + 1, pHelper->get_name() );
				}
				pLookAtCombo->set_min_max( 0, pImp->get_helper_count() );
				pLookAtCombo->end_editing( pOldUndo );

				// Update hero labels.
				pOldUndo = pHeroCombo->begin_editing( pUndo );
				pHeroCombo->clear_labels();
				for( uint32 i = 0; i < pImp->get_helper_count(); i++ ) {
					HelperC*	pHelper = pImp->get_helper( i );
					if( !pHelper )
						continue;
					pHeroCombo->add_label( i, pHelper->get_name() );
				}
				pHeroCombo->set_min_max( 0, pImp->get_helper_count() - 1 );
				pHeroCombo->end_editing( pOldUndo );
			}

			reset_vehicles();
			init_meshes();
		}
		else if( pParam->get_id() == ID_ATTRIBUTE_DUDE_COUNT )
		{
			reset_vehicles();
			init_meshes();
		}
		else if( pParam->get_id() == ID_ATTRIBUTE_LOOK_AT_OBJECT )
		{
			reset_vehicles();
			init_meshes();
		}
		else if( pParam->get_id() == ID_ATTRIBUTE_TEEN_COUNT )
		{
			reset_vehicles();
			init_meshes();
		}
		else if( pParam->get_id() == ID_ATTRIBUTE_ADULT_COUNT )
		{
			reset_vehicles();
			init_meshes();
		}
	}

	// Relay update
	return EffectI::update_notify( pCaller );
}

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

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

	DX9DeviceC*	pDevice = (DX9DeviceC*)pContext->query_interface( CLASS_DX9_DEVICEDRIVER );
	if( !pDevice )
		return;

	LPDIRECT3DDEVICE9	pD3DDevice = pDevice->get_d3ddevice();

	if( ui32Reason == INIT_DEVICE_CHANGED )
	{
		if( pDevice->get_state() == DEVICE_STATE_SHUTTINGDOWN )
		{
			if( m_pEffect )
			{
				m_pEffect->Release();
				m_pEffect = 0;
			}
		}

	}
	else if( ui32Reason == INIT_DEVICE_INVALIDATE )
	{
		// Invalidate device resources.
		if( m_pEffect )
			m_pEffect->OnLostDevice();
	}
	else if( ui32Reason == INIT_DEVICE_VALIDATE )
	{
		// Restore device resources.
		if( m_pEffect )
			m_pEffect->OnResetDevice();
	}
	else if( ui32Reason == INIT_INITIAL_UPDATE )
	{
		init_effect();
		init_obstacles( 0 );
		reset_vehicles();
		init_meshes();

		m_i32CurTime = 0;
		m_i32NextSaveTime = 0;
		m_f32SubFrame = 0.0f;
		m_i32SavedStateSize = 10;
	}
}


void
DX9CrowdEffectC::init_effect()
{
	DeviceContextC* pContext = m_pDemoInterface->get_device_context();
	// Get the DX9 device.
	DX9DeviceC*	pDevice = (DX9DeviceC*)pContext->query_interface( CLASS_DX9_DEVICEDRIVER );
	if( !pDevice )
		return;
	LPDIRECT3DDEVICE9	pD3DDevice = pDevice->get_d3ddevice();

	HRESULT	hr;

	LPD3DXBUFFER pError = NULL;

	if( FAILED( hr = D3DXCreateEffectFromResource( pD3DDevice, g_hInstance, MAKEINTRESOURCE( IDR_MESH_FX ), NULL, NULL, 0, NULL, &m_pEffect, &pError ) ) )
	{
		TRACE( "create effect failed\n" );
		if( pError )
			TRACE( "err: %s\n", pError->GetBufferPointer() );
	}

	if( pError )
		pError->Release();
}

void
DX9CrowdEffectC::init_obstacles( int32 i32Time )
{
	TimeContextC* pTimeContext = m_pDemoInterface->get_time_context();

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

	if( pHandle )
		pObsImp = (DX9ASEImportC*)pHandle->get_importable();

	if( pObsImp )
	{
		if( (uint32)m_vecObstacles.size() != pObsImp->get_helper_count() )
			m_vecObstacles.resize( pObsImp->get_helper_count() );

		int32		i32FPS, i32TicksPerFrame, i32FirstFrame, i32LastFrame;
		pObsImp->get_time_parameters( i32FPS, i32TicksPerFrame, i32FirstFrame, i32LastFrame );

		int32		i32Length = i32LastFrame - i32FirstFrame;
		int32		i32CurrentFrame = 0;

		if( i32Length > 0 )
		{
			i32Length *= i32TicksPerFrame;
			i32CurrentFrame = (int32)(pTimeContext->convert_time_to_fps( i32Time, i32FPS ) * (float64)i32TicksPerFrame);
			i32CurrentFrame %= i32Length;
			i32CurrentFrame += i32FirstFrame * i32TicksPerFrame;

			while( i32CurrentFrame < (i32FirstFrame * i32TicksPerFrame) )
				i32CurrentFrame += i32Length;
			while( i32CurrentFrame >= (i32LastFrame * i32TicksPerFrame) )
				i32CurrentFrame -= i32Length;
		}
		else
			i32CurrentFrame = 0;

		for( uint32 i = 0; i < pObsImp->get_helper_count(); i++ )
		{
			HelperC*	pHelper = pObsImp->get_helper( i );
			pHelper->eval_state( i32CurrentFrame );

			Vector3C	Pos = pHelper->get_center() + pHelper->get_position();
			m_vecObstacles[i].m_Pos[0] = Pos[0];
			m_vecObstacles[i].m_Pos[1] = Pos[2];
			m_vecObstacles[i].m_f32Radius = pHelper->get_radius();
		}
	}
}

bool
DX9CrowdEffectC::check_birth_pos( const Vector2C& Pos )
{
	if( m_vecObstacles.empty() )
		return true;

	float32	f32VecRad = 30.0f;

	for( uint32 i = 0; i < (uint32)m_vecObstacles.size(); i++ )
	{
		Vector2C	ObsPos = m_vecObstacles[i].m_Pos;
		float32		f32Rad = m_vecObstacles[i].m_f32Radius;
		Vector2C	Diff = Pos - ObsPos;
		if( Diff.length() < (f32VecRad + f32Rad) )
			return false;
	}

	for( uint32 i = 0; i < (uint32)m_vecVehicles.size(); i++ )
	{
		SimpleVehicleC*	pVec = m_vecVehicles[i];
		Vector2C	Diff = Pos - pVec->get_pos();
		if( Diff.length() < (f32VecRad + f32VecRad) )
			return false;
	}

	return true;
}

void
DX9CrowdEffectC::reset_vehicles()
{
	((ParamIntC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_DUDE_COUNT ))->get_val( 0, m_i32VehicleCount );

//	TRACE( "reset %d\n", m_i32VehicleCount );

	int32	i32Seed = 123;

	seed_gen_rand( i32Seed );

	// Clear old vehicles.
	for( uint32 i = 0; i < (uint32)m_vecVehicles.size(); i++ )
		delete m_vecVehicles[i];
	m_vecVehicles.clear();

	float32	f32BirthRadius = 450.0f;		// cm

	// Reset initialisation.

	int32	i32MaxTypeCount = m_i32VehicleCount / 5;
	if( i32MaxTypeCount < 1 )
		i32MaxTypeCount = 1;

	int32	i32Type = 0;
	int32	i32TypeCounter = 0;

	for( int i = 0; i < m_i32VehicleCount; i++ )
	{
		SimpleVehicleC*	pVec = new SimpleVehicleC;
		int32	i32Iter = 0;

		Vector2C	Pos;
		do
		{
			float32	f32Angle = (float32)(float_gen_rand() * M_PI * 2.0);
			float32	f32Radius = (float32)float_gen_rand() * f32BirthRadius;
			Pos[0] = (float32)cos( f32Angle ) * f32Radius;
			Pos[1] = (float32)sin( f32Angle ) * f32Radius;
			i32Iter++;
		}
		while( !check_birth_pos( Pos ) && i32Iter < 10 );

		pVec->init( Pos, i + 1, i32Type, 30.0f );

		i32TypeCounter++;
		if( i32TypeCounter >= i32MaxTypeCount )
		{
			i32TypeCounter = 0;
			if( i32Type < 4 )
				i32Type++;
		}

		m_vecVehicles.push_back( pVec );
	}

	// Reset saved states.
	if( (int32)m_vecSavedStates.size() < m_i32SavedStateSize )
	{
		m_vecSavedStates.resize( m_i32SavedStateSize );
		for( uint32 i = 0; i < (uint32)m_vecSavedStates.size(); i++ )
		{
			if( (int32)m_vecSavedStates[i].vecStates.size() < m_i32VehicleCount )
				m_vecSavedStates[i].vecStates.resize( m_i32VehicleCount );
		}
	}

	for( uint32 i = 0; i < (uint32)m_vecSavedStates.size(); i++ )
	{
		m_vecSavedStates[i].bActive = false;
		m_vecSavedStates[i].i32Time = 0;
		if( (int32)m_vecSavedStates[i].vecStates.size() != m_i32VehicleCount )
			m_vecSavedStates[i].vecStates.resize( m_i32VehicleCount );
	}

	m_i32CurTime = 0;
	m_i32CurState = 0;
	m_i32NextSaveTime = 0;
}

void
DX9CrowdEffectC::init_meshes()
{
	uint32	ui32FileIDs[5] = {
		ID_ATTRIBUTE_FILE_DUDE1_MESH,
		ID_ATTRIBUTE_FILE_DUDE2_MESH,
		ID_ATTRIBUTE_FILE_DUDE3_MESH,
		ID_ATTRIBUTE_FILE_DUDE4_MESH,
		ID_ATTRIBUTE_FILE_DUDE5_MESH
	};

	// Reset old instances.
	reset_meshes();

	for( uint32 i = 0; i < 5; i++ )
	{
		// The file has changed. Change Time.
		ParamFileC*		pFile = (ParamFileC*)m_pAttGizmo->get_parameter_by_id( ui32FileIDs[i] );
		if( !pFile )
			continue;

		FileHandleC*	pHandle = 0;
		DX9XImportC*	pImp = 0;

		pHandle = pFile->get_file( 0 );
		if( pHandle )
			pImp = (DX9XImportC*)pHandle->get_importable();

		if( pImp )
		{
			CD3DModel*	pModel = pImp->get_model();

			if( pModel )
			{
				for( uint32 j = 0; j < (uint32)m_vecVehicles.size(); j++ )
				{
					SimpleVehicleC*	pVec = m_vecVehicles[j];
					if( pVec->get_model_id() == i )
					{
						CD3DModelInstance*	pInst = 0;
						HRESULT	hr;
						if( FAILED( hr = pModel->CreateInstance( &pInst ) ) )
						{
							TRACE( "create instance\n" );
							pInst = 0;
						}
						pVec->set_model( pInst );
						TRACE( "set model %d id %d\n", j, i );
					}
				}
			}
		}
		else
			TRACE( "no imp %d\n", i );
	}

	// init hero
	{
		// The file has changed. Change Time.
		ParamFileC*		pFile = (ParamFileC*)m_pAttGizmo->get_parameter_by_id( ID_ATTRIBUTE_FILE_HERO_MESH );
		if( !pFile )
			return;

		FileHandleC*	pHandle = 0;
		DX9XImportC*	pImp = 0;

		pHandle = pFile->get_file( 0 );
		if( pHandle )
			pImp = (DX9XImportC*)pHandle->get_importable();

		if( pImp )
		{
			CD3DModel*	pModel = pImp->get_model();

			if( pModel )
			{
				HRESULT	hr;
				if( FAILED( hr = pModel->CreateInstance( &m_pHeroInstance ) ) )
				{
					TRACE( "create hero instance\n" );
					m_pHeroInstance = 0;
				}
			}
			else
				m_pHeroInstance = 0;
		}
		else
			TRACE( "no imp\n" );
	}
}

void
DX9CrowdEffectC::reset_meshes()
{
	for( uint32 i = 0; i < (uint32)m_vecVehicles.size(); i++ )
		m_vecVehicles[i]->set_model( 0 );

	// Delete old instance.
	if( m_pHeroInstance )
	{
		m_pHeroInstance->Release();
		m_pHeroInstance = 0;
	}
}

void
DX9CrowdEffectC::save_vehicle_states( int32 i32Time )
{
	if( m_vecSavedStates.empty() )
		return;

	m_i32CurState++;
	if( m_i32CurState >= (int32)m_vecSavedStates.size() )
		m_i32CurState = 0;

	SavedStateS&	SavedState = m_vecSavedStates[m_i32CurState];

	int	iIdx = 0;
	for( uint32 i = 0; i < (uint32)m_vecVehicles.size(); i++ )
	{
		SimpleVehicleC*	pVec = m_vecVehicles[i];
		SavedState.vecStates[iIdx] = pVec->get_state();
		iIdx++;
	}
	SavedState.bActive = true;
	SavedState.i32Time = i32Time;
}

void
DX9CrowdEffectC::restore_vehicle_states( PajaTypes::int32 i32Time )
{
	if( m_vecSavedStates.empty() )
		return;

	int32	i32Idx = -1;

	for( int32 i = 0; i < m_i32SavedStateSize ; i++ )
	{
		int32	i32CurIdx = m_i32CurState - i;
		if( i32CurIdx < 0 )
			i32CurIdx += m_i32SavedStateSize;

		if( m_vecSavedStates[i32CurIdx].bActive && m_vecSavedStates[i32CurIdx].i32Time < i32Time )
		{
			i32Idx = i32CurIdx;
			break;
		}
	}

	if( i32Idx < 0 || i32Idx >= (int32)m_vecSavedStates.size() )
	{
		TRACE( "index out of bounds: %d [%d..%d[\n", i32Idx, 0, m_vecSavedStates.size() );
		reset_vehicles();
		init_meshes();
		return;
	}

	SavedStateS&	SavedState = m_vecSavedStates[i32Idx];

	int	iIdx = 0;
	for( uint32 i = 0; i < (uint32)m_vecVehicles.size(); i++ )
	{
		SimpleVehicleC*	pVec = m_vecVehicles[i];
		pVec->set_state( SavedState.vecStates[iIdx] );
		iIdx++;
	}
	SavedState.bActive = true;
	m_i32CurTime = SavedState.i32Time;
	m_i32CurState = i32Idx;
	m_i32NextSaveTime = m_i32CurTime + SAVE_TIME_STEP;

//	TRACE( "restored to: %d (req: %d)\n", m_i32CurTime, i32Time );
}

void
DX9CrowdEffectC::update_vehicles( int32 i32Time )
{
	int32	i32MaxIter = 1000;
	int32	i32Iter = 0;

	float32	f32DT = (float32)TIME_STEP / 1000.0f;

	// Get audio spectrum.
	DeviceContextC*	pContext = m_pDemoInterface->get_device_context();
	TimeContextC*		pTimeContext = m_pDemoInterface->get_time_context();
	AudioSpectrumC*	pSpec = (AudioSpectrumC*)pContext->query_interface( CLASS_AUDIO_SPECTRUM );

	if( i32Time < (m_i32CurTime - TIME_STEP) )
	{
		if( !m_vecSavedStates.empty() )
		{
			restore_vehicle_states( i32Time );
		}
		else
		{
			reset_vehicles();
			init_meshes();
		}
	}

	while( m_i32CurTime <= i32Time && i32Iter < i32MaxIter )
	{
		m_i32CurTime += TIME_STEP;
		i32Iter++;

		float32	f32HeightForce = 0;
		if( pSpec )
		{
			int32	i32PajaTime = (int32)pTimeContext->convert_fps_to_time( i32Time, 1000.0f );
			pSpec->eval_state( i32PajaTime, pTimeContext );
			f32HeightForce = pSpec->get_band_value( 1 );
			f32HeightForce *= f32HeightForce * f32HeightForce;
		}

		float32	f32BassAmount = 0;
		((ParamFloatC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_BASS_AFFECT ))->get_val( i32Time, f32BassAmount );

		f32HeightForce *= f32BassAmount;

		bool			bLookAt = false;
		Vector2C	LookAtPos;
		if( m_i32LookAtObj >= 0 && m_i32LookAtObj < (int32)m_vecObstacles.size() )
		{
			bLookAt = true;
			LookAtPos = m_vecObstacles[m_i32LookAtObj].m_Pos;
		}

		// Calc collisions.
		for( uint32 i = 0; i < (uint32)m_vecVehicles.size(); i++ )
		{
			SimpleVehicleC*	pVec = m_vecVehicles[i];
			for( uint32 j = i + 1; j < (uint32)m_vecVehicles.size(); j++ )
			{
				SimpleVehicleC*	pVec2 = m_vecVehicles[j];
				if( pVec == pVec2 )
					continue;
				int32	i32DX = pVec->get_grid_x() - pVec2->get_grid_x();
				int32	i32DY = pVec->get_grid_y() - pVec2->get_grid_y();
				if( i32DX >= -1 && i32DX <= 1 && i32DY >= -1 && i32DY <= 1 )
					pVec->handle_collision( pVec2 );
			}

			pVec->handle_collision( m_vecObstacles );
		}

		// Update
		for( int32 i = 0; i < (uint32)m_vecVehicles.size(); i++ )
		{
			if( i < m_i32AdultCount )
				m_vecVehicles[i]->set_age( 2 );
			else if( i < m_i32TeenCount )
				m_vecVehicles[i]->set_age( 1 );

			if( bLookAt )
				m_vecVehicles[i]->set_look_at( LookAtPos );

			m_vecVehicles[i]->set_height_force( f32HeightForce );

			m_vecVehicles[i]->update( f32DT );
		}

		if( m_i32CurTime >= m_i32NextSaveTime )
		{
			save_vehicle_states( m_i32CurTime );
			m_i32NextSaveTime += SAVE_TIME_STEP;
		}
	}

	if( m_i32CurTime > i32Time )
	{
		m_f32SubFrame = (float32)(m_i32CurTime - i32Time) / (float32)TIME_STEP;
		if( m_f32SubFrame > 1.0f )
			m_f32SubFrame = 1.0f;
		if( m_f32SubFrame < 0.0f )
			m_f32SubFrame = 0.0f;
		m_f32SubFrame = 1.0f - m_f32SubFrame;
	}
	else
		m_f32SubFrame = 0.0f;
}

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

	if( !m_pDemoInterface )
		return;

	// Requires multitexturing and bledn equation.
	DeviceContextC* pContext = m_pDemoInterface->get_device_context();
	TimeContextC* pTimeContext = m_pDemoInterface->get_time_context();


	Matrix2C	rPosMat, rScaleMat;

	Vector2C	rScale;
	Vector2C	rPos;

	// 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_SCALE ))->get_val( i32Time, rScale );

	// Get the size from the file or use the defaults if no file.
	DX9ASEImportC*	pObsImp = 0;
	FileHandleC*		pHandle;
	int32				i32ObsFileTime;
	((ParamFileC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_FILE_OBSTACLES ))->get_file( i32Time, pHandle, i32ObsFileTime );

	if( pHandle )
		pObsImp = (DX9ASEImportC*)pHandle->get_importable();


	// Reset bounding box and TM.
	m_rBBox[0] = Vector2C( 0, 0 );
	m_rBBox[1] = Vector2C( 0, 0 );
	m_rTM.set_identity();


	m_i32HeroObj = 0;
	m_i32LookAtObj = -1;
	float32	f32HeroAnimTime = 0.0f;

	((ParamIntC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_LOOK_AT_OBJECT ))->get_val( i32Time, m_i32LookAtObj );
	((ParamIntC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_HERO_OBJECT ))->get_val( i32Time, m_i32HeroObj );
	((ParamIntC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_TEEN_COUNT ))->get_val( i32Time, m_i32TeenCount );
	((ParamIntC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_ADULT_COUNT ))->get_val( i32Time, m_i32AdultCount );
	((ParamIntC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_DUDE_COUNT ))->get_val( i32Time, m_i32VehicleCount );
	((ParamFloatC*)m_pAttGizmo->get_parameter( ID_ATTRIBUTE_HERO_ANIM_TIME ))->get_val( i32Time, f32HeroAnimTime );

	m_i32LookAtObj -= 1;



	// Get the D3D device.
	DX9DeviceC*	pDevice = (DX9DeviceC*)pContext->query_interface( CLASS_DX9_DEVICEDRIVER );
	if( !pDevice )
		return;

	LPDIRECT3DDEVICE9	pD3DDevice = pDevice->get_d3ddevice();

	// Get the OpenGL viewport.
	DX9ViewportC*	pViewport = (DX9ViewportC*)pDevice->query_interface( GRAPHICSDEVICE_VIEWPORT_INTERFACE );
	if( !pViewport )
		return;

	// Find camera object.
	D3DXMATRIX	matWorld;

	LayerC*	pParent = get_parent();
	if( !pParent )
		return;

	DX9CameraEffectC*	pCamEffect = 0;
	DX9DayLightEffectC*	pLightEffect = 0;

	// Find first visible camera.
	for( int32 i = 0; i < pParent->get_effect_count(); i++ )
	{
		EffectI*	pEffect = pParent->get_effect( i );
		if( pEffect->get_class_id() == CLASS_DX9CAMERA_EFFECT )
		{
			if( !pCamEffect && pEffect->get_timesegment()->is_visible( i32Time ) )
			{
				pCamEffect = (DX9CameraEffectC*)pEffect;
			}
		}
		else if( pEffect->get_class_id() == CLASS_DX9DAYLIGHT_EFFECT )
		{
			if( !pLightEffect && pEffect->get_timesegment()->is_visible( i32Time ) )
			{
				pLightEffect = (DX9DayLightEffectC*)pEffect;
			}
		}
	}

	if( !pCamEffect || !pLightEffect )
		return;

	D3DXMATRIX*	pMatView = pCamEffect->get_camera_matrix();
	D3DXMATRIX*	pMatProj = pCamEffect->get_proj_matrix();
	float32			f32FocalDistance = pCamEffect->get_focal_distance();
	float32			f32FocalRange = pCamEffect->get_focal_range();


	pD3DDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW );

	pD3DDevice->SetRenderState( D3DRS_LIGHTING, FALSE );
	pD3DDevice->SetRenderState( D3DRS_ALPHATESTENABLE, FALSE );
	pD3DDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_SOLID );
	pD3DDevice->SetRenderState( D3DRS_STENCILENABLE, FALSE );
	pD3DDevice->SetRenderState( D3DRS_CLIPPING, TRUE );
	pD3DDevice->SetRenderState( D3DRS_CLIPPLANEENABLE, FALSE );
	pD3DDevice->SetRenderState( D3DRS_VERTEXBLEND, D3DVBF_DISABLE );
	pD3DDevice->SetRenderState( D3DRS_INDEXEDVERTEXBLENDENABLE, FALSE );
	pD3DDevice->SetRenderState( D3DRS_FOGENABLE, FALSE );
	pD3DDevice->SetRenderState( D3DRS_COLORWRITEENABLE,
			D3DCOLORWRITEENABLE_RED  | D3DCOLORWRITEENABLE_GREEN |
			D3DCOLORWRITEENABLE_BLUE | D3DCOLORWRITEENABLE_ALPHA );

	pD3DDevice->SetRenderState( D3DRS_ZENABLE, TRUE );
	pD3DDevice->SetRenderState( D3DRS_ZWRITEENABLE, TRUE );
	pD3DDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE );
	
	pD3DDevice->SetRenderState( D3DRS_BLENDOP, D3DBLENDOP_ADD );
	pD3DDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA );
	pD3DDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );


	bool	bFog = false;

	if( pCamEffect->get_fog_type() != 0 )
	{
		float32	f32Start = pCamEffect->get_fog_start();
		float32	f32End = pCamEffect->get_fog_end();
		ColorC	Col = pCamEffect->get_fog_color();
		DWORD	dwCol = D3DCOLOR_ARGB( 255, (int32)(Col[0] * 255), (int32)(Col[1] * 255), (int32)(Col[2] * 255) );

		pD3DDevice->SetRenderState( D3DRS_FOGENABLE, TRUE );
		pD3DDevice->SetRenderState( D3DRS_FOGTABLEMODE, D3DFOG_LINEAR );
		pD3DDevice->SetRenderState( D3DRS_FOGSTART, *(DWORD *)(&f32Start) );
		pD3DDevice->SetRenderState( D3DRS_FOGEND,   *(DWORD *)(&f32End) );
		pD3DDevice->SetRenderState( D3DRS_FOGCOLOR, dwCol );

		bFog = true;
	}
	else
	{
		pD3DDevice->SetRenderState( D3DRS_FOGENABLE, FALSE );
	}



	XYZCOLOR_VERTEX	rCircle[16];
	XYZCOLOR_VERTEX	rQuad[4];
	pD3DDevice->SetFVF( FVF_XYZCOLOR_VERTEX );
	pD3DDevice->SetPixelShader( NULL );


	int32	i32TimeMilliSec = (int32)pTimeContext->convert_time_to_fps( i32Time, 1000.0f );

	init_obstacles( i32Time );

	update_vehicles( i32TimeMilliSec );


	D3DXMatrixIdentity( &matWorld );
	pD3DDevice->SetTransform( D3DTS_WORLD, &matWorld );

/*
	// Draw obstancles.
	if( !m_vecObstacles.empty() )
	{
		for( uint32 i = 0; i < (uint32)m_vecObstacles.size(); i++ )
		{
			Vector2C	Pos = m_vecObstacles[i].m_Pos;
			Vector2C	Vec;
			float32		f32Rad = m_vecObstacles[i].m_f32Radius;

			DWORD	dwCol = D3DCOLOR_ARGB( 255, 0, 0, 255 );

			for( uint32 j = 0; j < 16; j++ )
			{
				float32	f32A = (float32)j / 15.0f * 2.0f * (float32)M_PI;
				Vec[0] = Pos[0] + (float32)cos( f32A ) * f32Rad;
				Vec[1] = Pos[1] + (float32)sin( f32A ) * f32Rad;
				rCircle[j].x = Vec[0];
				rCircle[j].y = 0;
				rCircle[j].z = Vec[1];
				rCircle[j].color = dwCol;
			}
			pD3DDevice->DrawPrimitiveUP( D3DPT_LINESTRIP, 15, rCircle, sizeof( XYZCOLOR_VERTEX ) );
		}
	}
*/
/*
	for( uint32 i = 0; i < (uint32)m_vecVehicles.size(); i++ )
	{
		SimpleVehicleC*	pVec = m_vecVehicles[i];

		Vector2C	OldPos = pVec->get_old_pos();
		Vector2C	Pos = pVec->get_pos();
		Vector2C	TopOldPos = pVec->get_top_old_pos();
		Vector2C	TopPos = pVec->get_top_pos();
		float32		f32OldHeading = pVec->get_old_heading();
		float32		f32Heading = pVec->get_heading();

		Pos = OldPos + (Pos - OldPos) * m_f32SubFrame;
		TopPos = TopOldPos + (TopPos - TopOldPos) * m_f32SubFrame;

		float	f32Diff = f32Heading - f32OldHeading;
		// Find shortest change.
		while( f32Diff > M_PI )
			f32Diff -= (float)M_PI * 2.0f;
		while( f32Diff < -M_PI )
			f32Diff += (float)M_PI * 2.0f;
		f32Heading = f32OldHeading + f32Diff * m_f32SubFrame;

		Vector2C	Dir( (float32)cos( f32Heading ), (float32)sin( f32Heading ) );
		Vector2C	DirLeft( (float32)cos( f32Heading + M_PI/2 ), (float32)sin( f32Heading + M_PI/2 ) );
		Vector2C	DirRight( (float32)cos( f32Heading - M_PI/2 ), (float32)sin( f32Heading - M_PI/2 ) );
		Vector2C	Vec;

		int32	i32Alpha = 127 + pVec->get_age() * 64;

		DWORD	dwCol = D3DCOLOR_ARGB( i32Alpha, 255, 255, 255 );
		if( pVec->get_task() == VEC_TASK_WANDER )
			dwCol = D3DCOLOR_ARGB( i32Alpha, 255, 255, 0 );
		else if( pVec->get_task() == VEC_TASK_CHAT )
			dwCol = D3DCOLOR_ARGB( i32Alpha, 255, 0, 0 );
		else if( pVec->get_task() == VEC_TASK_STARE )
			dwCol = D3DCOLOR_ARGB( i32Alpha, 0, 255, 0 );
		else if( pVec->get_task() == VEC_TASK_LOOKAT )
			dwCol = D3DCOLOR_ARGB( i32Alpha, 0, 0, 255 );

		for( uint32 j = 0; j < 16; j++ )
		{
			float32	f32A = (float32)j / 15.0f * 2.0f * (float32)M_PI;
			Vec[0] = (float32)cos( f32A ) * 30.0f;
			Vec[1] = (float32)sin( f32A ) * 30.0f;
			rCircle[j].x = Vec[0] + TopPos[0];
			rCircle[j].y = 0;
			rCircle[j].z = Vec[1] + TopPos[1];
			rCircle[j].color = dwCol;
		}
		pD3DDevice->DrawPrimitiveUP( D3DPT_LINESTRIP, 15, rCircle, sizeof( XYZCOLOR_VERTEX ) );

		Pos -= Dir * 30.0f;

		Vec = Pos + Dir * 80.0f;
		rQuad[0].x = Vec[0];
		rQuad[0].y = 0;
		rQuad[0].z = Vec[1];
		rQuad[0].color = dwCol;

		Vec = Pos + DirRight * 30.0f;
		rQuad[1].x = Vec[0];
		rQuad[1].y = 0;
		rQuad[1].z = Vec[1];
		rQuad[1].color = dwCol;

		Vec = Pos + DirLeft * 30.0f;
		rQuad[2].x = Vec[0];
		rQuad[2].y = 0;
		rQuad[2].z = Vec[1];
		rQuad[2].color = dwCol;

		pD3DDevice->DrawPrimitiveUP( D3DPT_TRIANGLEFAN, 1, rQuad, sizeof( XYZCOLOR_VERTEX ) );
	}
*/

	if( !m_pEffect )
	{
		TRACE( "no effect!!\n" );
		return;
	}

  // Set Light for vertex shader
  D3DXVECTOR4 vLightDir( 0.0f, 1.0f, -1.0f, 0.0f );
	D3DXVECTOR4 vLightColor( 1, 1, 1, 1 );

	// Primary
	LightC*	pPrimaryLight = pLightEffect->get_primary_light();
	if( pPrimaryLight )
	{
		Vector3C	Pos = pPrimaryLight->get_position();
		Vector3C	Target = pPrimaryLight->get_target();
		Vector3C	Dir = (Pos - Target).normalize();
		ColorC		Col = pPrimaryLight->get_color() * pPrimaryLight->get_multiplier();
		vLightDir.x = Dir[0];
		vLightDir.y = Dir[1];
		vLightDir.z = Dir[2];
		vLightColor.x = Col[0];
		vLightColor.y = Col[1];
		vLightColor.z = Col[2];
	  m_pEffect->SetVector( "light1Dir", &vLightDir );
	  m_pEffect->SetVector( "light1Diffuse", &vLightColor );
	}
	else
	{
		vLightColor.x = 0;
		vLightColor.y = 0;
		vLightColor.z = 0;
	  m_pEffect->SetVector( "light1Diffuse", &vLightColor );
	}

	// Fill 1
	LightC*	pFill1Light = pLightEffect->get_fill_light_1();
	if( pFill1Light )
	{
		Vector3C	Pos = pFill1Light->get_position();
		Vector3C	Target = pFill1Light->get_target();
		Vector3C	Dir = (Pos - Target).normalize();
		ColorC		Col = pFill1Light->get_color() * pFill1Light->get_multiplier();
		vLightDir.x = Dir[0];
		vLightDir.y = Dir[1];
		vLightDir.z = Dir[2];
		vLightColor.x = Col[0];
		vLightColor.y = Col[1];
		vLightColor.z = Col[2];
	  m_pEffect->SetVector( "light2Dir", &vLightDir );
	  m_pEffect->SetVector( "light2Diffuse", &vLightColor );
	}
	else
	{
		vLightColor.x = 0;
		vLightColor.y = 0;
		vLightColor.z = 0;
	  m_pEffect->SetVector( "light2Diffuse", &vLightColor );
	}

	// Fill 2
	LightC*	pFill2Light = pLightEffect->get_fill_light_2();
	if( pFill2Light )
	{
		Vector3C	Pos = pFill2Light->get_position();
		Vector3C	Target = pFill2Light->get_target();
		Vector3C	Dir = (Pos - Target).normalize();
		ColorC		Col = pFill2Light->get_color() * pFill2Light->get_multiplier();
		vLightDir.x = Dir[0];
		vLightDir.y = Dir[1];
		vLightDir.z = Dir[2];
		vLightColor.x = Col[0];
		vLightColor.y = Col[1];
		vLightColor.z = Col[2];
	  m_pEffect->SetVector( "light3Dir", &vLightDir );
	  m_pEffect->SetVector( "light3Diffuse", &vLightColor );
	}
	else
	{
		vLightColor.x = 0;
		vLightColor.y = 0;
		vLightColor.z = 0;
	  m_pEffect->SetVector( "light3Diffuse", &vLightColor );
	}

  // Setup the projection matrix
  D3DXMATRIXA16	matProjTrans;

	{
		HRESULT	hr;
		if( FAILED( hr = m_pEffect->SetTechnique( "tBasic" ) ) )
			TRACE( "!!!set tech 'tBasic' failed\n" );
	}

  m_pEffect->SetMatrix( "mViewProj", pMatProj );
  m_pEffect->SetVector( "lhtDir", &vLightDir );

	// Set the pixel shader output based on the extra buffer.
	uint32	ui32PSIndex = 0;
	if( pDevice->get_flags() & GRAPHICSBUFFER_INIT_EXTRA )
		ui32PSIndex = 1;

	m_pEffect->SetInt( "NumRenderTarget", ui32PSIndex );

	if( f32FocalRange < 0.0001f )
		f32FocalRange = 0.0001f;

	// Set DOF parameters.
	m_pEffect->SetFloat( "focalDist", f32FocalDistance );
	m_pEffect->SetFloat( "focalRange", 1.0f / f32FocalRange );


	RenderContextS	RC;
	RC.iTime = i32Time;
	RC.pDevice = pDevice;
	RC.pEffect = m_pEffect;
	RC.pMatView = pMatView;

	// Setup tex stage
  pD3DDevice->SetTextureStageState( 0, D3DTSS_COLOROP,   D3DTOP_MODULATE );
  pD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
  pD3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );
  pD3DDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP,   D3DTOP_MODULATE );
  pD3DDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE );
  pD3DDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE );
  pD3DDevice->SetTextureStageState( 0, D3DTSS_TEXCOORDINDEX, 0 );
  pD3DDevice->SetTextureStageState( 0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE );

  pD3DDevice->SetTextureStageState( 1, D3DTSS_COLOROP,   D3DTOP_MODULATE );
  pD3DDevice->SetTextureStageState( 1, D3DTSS_COLORARG1, D3DTA_TEXTURE );
  pD3DDevice->SetTextureStageState( 1, D3DTSS_COLORARG2, D3DTA_DIFFUSE );
  pD3DDevice->SetTextureStageState( 1, D3DTSS_ALPHAOP,   D3DTOP_MODULATE );
  pD3DDevice->SetTextureStageState( 1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE );
  pD3DDevice->SetTextureStageState( 1, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE );
  pD3DDevice->SetTextureStageState( 1, D3DTSS_TEXCOORDINDEX, 1 );
  pD3DDevice->SetTextureStageState( 1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE );

	for( uint32 i = 0; i < (uint32)m_vecVehicles.size(); i++ )
	{
		SimpleVehicleC*	pVec = m_vecVehicles[i];

		Vector2C	OldPos = pVec->get_old_pos();
		Vector2C	Pos = pVec->get_pos();
		Vector2C	TopOldPos = pVec->get_top_old_pos();
		Vector2C	TopPos = pVec->get_top_pos();
		float32		f32OldHeading = pVec->get_old_heading();
		float32		f32Heading = pVec->get_heading();

		Pos = OldPos + (Pos - OldPos) * m_f32SubFrame;
		TopPos = TopOldPos + (TopPos - TopOldPos) * m_f32SubFrame;

		float	f32Diff = f32Heading - f32OldHeading;
		// Find shortest change.
		while( f32Diff > M_PI )
			f32Diff -= (float)M_PI * 2.0f;
		while( f32Diff < -M_PI )
			f32Diff += (float)M_PI * 2.0f;
		f32Heading = f32OldHeading + f32Diff * m_f32SubFrame;

		D3DXMATRIX	matTrans;
		D3DXMATRIX	matScale;
		D3DXMATRIX	matRot;

		D3DXVECTOR3	Axis;
		Axis.x = TopPos[0] - Pos[0];
		Axis.y = 50;
		Axis.z = TopPos[1] - Pos[1];
		D3DXVec3Normalize( &Axis, &Axis );
		D3DXMatrixRotationAxis( &matRot, &Axis, -f32Heading );

		float32	f32Height = pVec->get_height();
		float32	f32Scale = 1.0f;
		if( f32Height < 0 )
		{
			f32Scale = 0.8f + (0.2f * (f32Height + 1.0f));
			f32Height = 0;
		}

		D3DXMatrixTranslation( &matTrans, Pos[0], f32Height * 10.0f, Pos[1] );
		D3DXMatrixScaling( &matScale, 1.2f, 1.2f * f32Scale, 1.2f );

		D3DXMatrixMultiply( &matWorld, &matScale, &matRot );
		D3DXMatrixMultiply( &matWorld, &matWorld, &matTrans );

		pD3DDevice->SetTransform( D3DTS_WORLD, &matWorld );

		if( pVec->get_model() )
		{
			CD3DModelInstance*	pInst = pVec->get_model();
			pInst->Update( pVec->get_age_anim() * pInst->GetAnimMaxTime() * 0.5f * 0.99f, &matWorld );
			pInst->Draw( &RC );
//			TRACE( "anim time: %f\n", pVec->get_age_anim() );
		}
		else
		{
			TRACE( "no model (%d)\n ", i );
		}

	}

	if( pObsImp && m_pHeroInstance )
	{

		// If "Frame" parameter is animated, use it instead of FPS setting.
		ParamFloatC*	pParamFrame = (ParamFloatC*)m_pTimeGizmo->get_parameter_by_id( ID_TIME_FRAME );
		ControllerC*	pCont = pParamFrame->get_controller();
		if( pCont && pCont->get_key_count() )
		{
			float32	f32Frame;
			pParamFrame->get_val( i32Time, f32Frame );

			float32	f32StartFrame = pObsImp->get_start_label();
			float32	f32EndFrame = pObsImp->get_end_label();

			if( (f32EndFrame - f32StartFrame) >= 0 ) {
				i32ObsFileTime = (int32)(((f32Frame - f32StartFrame) / (f32EndFrame - f32StartFrame)) * pObsImp->get_duration());
			}
		}

		pObsImp->eval_state( i32ObsFileTime );

		int32	i32CurrentFrame = pObsImp->get_current_frame();

/*
		int32		i32FPS, i32TicksPerFrame, i32FirstFrame, i32LastFrame;
		pObsImp->get_time_parameters( i32FPS, i32TicksPerFrame, i32FirstFrame, i32LastFrame );

		int32		i32Length = i32LastFrame - i32FirstFrame;
		int32		i32CurrentFrame = 0;


		if( i32Length > 0 )
		{
			i32Length *= i32TicksPerFrame;
			i32CurrentFrame = (int32)(pTimeContext->convert_time_to_fps( i32Time, i32FPS ) * (float64)i32TicksPerFrame);
			i32CurrentFrame %= i32Length;
			i32CurrentFrame += i32FirstFrame * i32TicksPerFrame;

			while( i32CurrentFrame < (i32FirstFrame * i32TicksPerFrame) )
				i32CurrentFrame += i32Length;
			while( i32CurrentFrame >= (i32LastFrame * i32TicksPerFrame) )
				i32CurrentFrame -= i32Length;
		}
		else
			i32CurrentFrame = 0;
*/

		if( m_i32HeroObj >= 0 && m_i32HeroObj < pObsImp->get_helper_count() )
		{
			HelperC*	pHelper = pObsImp->get_helper( m_i32HeroObj );
			pHelper->eval_state( i32CurrentFrame );
			Matrix3C	TM = pHelper->get_tm();

			Vector3C	Pos = pHelper->get_position();
			QuatC			Rot = pHelper->get_rotation();
			Vector3C	RotAxis;
			float32		f32RotAngle;

			Rot.to_axis_angle( RotAxis, f32RotAngle );

//			D3DXMatrixTranslation( &matWorld, Pos[0], Pos[1], Pos[2] );

			D3DXVECTOR3	Axis;

			Axis.x = RotAxis[0];
			Axis.y = RotAxis[1];
			Axis.z = RotAxis[2];

			D3DXMATRIX	matTrans;
			D3DXMATRIX	matRot;
			D3DXMatrixRotationAxis( &matRot, &Axis, f32RotAngle );
			D3DXMatrixTranslation( &matTrans, Pos[0], Pos[1], Pos[2] );
			D3DXMatrixMultiply( &matWorld, &matRot, &matTrans );

			pD3DDevice->SetTransform( D3DTS_WORLD, &matWorld );

			if( f32HeroAnimTime < 0.0f )
				f32HeroAnimTime = 0.0f;
			else if( f32HeroAnimTime > 1.0f )
				f32HeroAnimTime = 1.0f;

			m_pHeroInstance->Update( m_pHeroInstance->GetAnimMaxTime() * f32HeroAnimTime * 0.99f, &matWorld );
			m_pHeroInstance->Draw( &RC );
		}
	}
/*	else
	{
		TRACE( "no hero or obs\n" );
	}*/

	if( bFog )
	{
		pD3DDevice->SetRenderState( D3DRS_FOGENABLE, FALSE );
	}

	pD3DDevice->SetRenderState( D3DRS_ZENABLE, TRUE );
	pD3DDevice->SetRenderState( D3DRS_ZWRITEENABLE, TRUE );

}

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

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

bool
DX9CrowdEffectC::hit_test( const Vector2C& rPoint )
{
	return m_rBBox.contains( rPoint );
//	return false;
}


enum DX9CrowdEffectChunksE {
	CHUNK_IMAGE_BASE =				0x10,
	CHUNK_IMAGE_TRANSGIZMO =	0x20,
	CHUNK_IMAGE_ATTRIBGIZMO =	0x30,
	CHUNK_IMAGE_TIMEGIZMO =	0x40,
};

const uint32	IMAGE_VERSION = 1;

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

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

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

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

	// Time
	pSave->begin_chunk( CHUNK_IMAGE_TIMEGIZMO, IMAGE_VERSION );
		ui32Error = m_pTimeGizmo->save( pSave );
	pSave->end_chunk();

	return ui32Error;
}

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

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

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

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

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

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

		default:
			assert( 0 );
		}

		pLoad->close_chunk();

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

	return ui32Error;
}
