/*!
	@file RuEntity_Particle_Visualizer_Sprite.cpp

	Copyright (c) 2004-2005 All rights reserved
*/

#include "../../Scene/Particle/RuEntity_Particle_Visualizer_Sprite.h"

#pragma unmanaged

// ************************************************************************************************************************************************************

ruRTTI_DEFINE_SUBCLASS(CRuEntity_Particle_Visualizer_Sprite, IRuEntity_Particle_Visualizer)
ruHEAP_DEFINE_SUBCLASS(CRuEntity_Particle_Visualizer_Sprite, "CRuEntity_Particle_Visualizer_Sprite", "IRuEntity_Particle_Visualizer")
ruCLASSGUID_DEFINE(CRuEntity_Particle_Visualizer_Sprite, 0xA0EE1154, 0xB6364ba0, 0xAD532783, 0x1B88EA31)

// ************************************************************************************************************************************************************

CRuEntity_Particle_Visualizer_Sprite::CRuEntity_Particle_Visualizer_Sprite()
:	m_orientation(ruAXIS_Z),
	m_billboardType(ruBILLBOARDTYPE_UNCONSTRAINED),
	m_blendType(ruALPHAMODE_NORMAL),
	m_spriteTextureName(NULL),
	m_spriteTexture(NULL),
	m_spriteSubTextureWidth(16),
	m_spriteSubTextureHeight(16),
	m_frameRate(1.0f),
	m_numFrames(1),
	m_randomRotation(FALSE),
	m_quadScale(1.0f, 1.0f, 0.0f),
	m_emitter(NULL),
	m_maxParticlesPerBatch(70)
{
	// Create model
	m_model = ruNEW CRuModel();

	// Disable automatic cloning and serialization
	m_model->SetOption(ruENTITYOPTION_NOCLONE, TRUE);
	m_model->SetOption(ruENTITYOPTION_NOSERIALIZE, TRUE);

	this->AddChild(m_model);

	// Create mesh to use for rendering particle batches
	m_mesh = ruNEW CRuMesh();
	m_mesh->Initialize(ruPRIMTYPE_TRIANGLELIST, 4 * m_maxParticlesPerBatch, 2 * m_maxParticlesPerBatch, ruVERTEXFORMAT_POSITION | ruVERTEXFORMAT_DIFFUSE, 1);

	BuildMesh();

	CRuColor4 color(1.0f, 1.0f, 1.0f, 1.0f);
	m_track_Color.InsertKey(0.0f, color);
	m_track_Color.InsertKey(1.0f, color);

	REAL alpha = 1.0f;
	m_track_Alpha.InsertKey(0.0f, alpha);
	m_track_Alpha.InsertKey(1.0f, alpha);

	REAL scale = 1.0f;
	m_track_Scale.InsertKey(0.0f, scale);
	m_track_Scale.InsertKey(1.0f, scale);

	REAL speed = 0.0f;
	m_track_RotationSpeed.InsertKey(0.0f, speed);
	m_track_RotationSpeed.InsertKey(1.0f, speed);

	// Register self as an event listener
	this->RegisterListener(this, NULL);
}

CRuEntity_Particle_Visualizer_Sprite::~CRuEntity_Particle_Visualizer_Sprite()
{
	// Clear all batches
	ClearBatches();

	// Release particle emitter
	ruSAFE_RELEASE(m_emitter);

	// Release model
	ruSAFE_RELEASE(m_model);

	// Release mesh
	ruSAFE_RELEASE(m_mesh);

	// Release texture name string
	delete m_spriteTextureName;

	// Release sprite texture
	ruSAFE_RELEASE(m_spriteTexture);

	// Release resources used by batch structures
	while(m_freeBatchDescStack.IsEmpty() == FALSE)
	{
		delete m_freeBatchDescStack.Pop();
	}
}

IRuObject *CRuEntity_Particle_Visualizer_Sprite::Clone(IRuObject *clonedObject)
{
	// Create a new cloned entity if necessary
	clonedObject = clonedObject ? clonedObject : ruNEW CRuEntity_Particle_Visualizer_Sprite();

	// Chain cloning call to parent class
	IRuEntity_Particle_Visualizer::Clone(clonedObject);

	// Shortcut reference to the cloned entity
	CRuEntity_Particle_Visualizer_Sprite *clonedPSys = static_cast<CRuEntity_Particle_Visualizer_Sprite *>(clonedObject);

	// Copy visualizer settings
	clonedPSys->m_orientation = m_orientation;
	clonedPSys->m_billboardType = m_billboardType;
	clonedPSys->m_blendType = m_blendType;

	if(m_spriteTextureName)
	{
		clonedPSys->m_spriteTextureName = ruNEW char [strlen(m_spriteTextureName) + 1];
		strcpy(clonedPSys->m_spriteTextureName, m_spriteTextureName);
	}

	if(m_spriteTexture)
	{
		clonedPSys->m_spriteTexture = m_spriteTexture;
		clonedPSys->m_spriteTexture->AddRef();
	}

	clonedPSys->m_spriteSubTextureWidth = m_spriteSubTextureWidth;
	clonedPSys->m_spriteSubTextureHeight = m_spriteSubTextureHeight;
	clonedPSys->m_frameRate = m_frameRate;
	clonedPSys->m_numFrames = m_numFrames;
	clonedPSys->m_orientToDirectionOfTravel = m_orientToDirectionOfTravel;
	clonedPSys->m_randomRotation = m_randomRotation;
	clonedPSys->m_quadOffset = m_quadOffset;
	clonedPSys->m_quadScale = m_quadScale;

	m_track_Color.CopyTo(&clonedPSys->m_track_Color);
	m_track_Alpha.CopyTo(&clonedPSys->m_track_Alpha);
	m_track_Scale.CopyTo(&clonedPSys->m_track_Scale);
	m_track_RotationSpeed.CopyTo(&clonedPSys->m_track_RotationSpeed);

	clonedPSys->m_maxParticlesPerBatch = m_maxParticlesPerBatch;

	// Clone the emitter
	if(m_emitter)
	{
		IRuObject *clonedEmitter = m_emitter->Clone();
		if(clonedEmitter && clonedEmitter->GetType().IsSubClassOf(IRuEntity_Particle_Emitter::Type))
		{
			clonedPSys->m_emitter = static_cast<IRuEntity_Particle_Emitter *>(clonedEmitter);

			// Disable automatic cloning and serialization
			clonedPSys->m_emitter->SetOption(ruENTITYOPTION_NOCLONE, TRUE);
			clonedPSys->m_emitter->SetOption(ruENTITYOPTION_NOSERIALIZE, TRUE);

			clonedPSys->AddChild(clonedPSys->m_emitter);
		}
	}

	return clonedPSys;
}

BOOL CRuEntity_Particle_Visualizer_Sprite::SerializeFrom(IRuStream *stream)
{
	CRuGUID classGUID;
	BOOL emitterAvailable = FALSE;

	// Legacy stuff
	DWORD startColor;
	DWORD endColor;
	REAL startScale;
	REAL endScale;
	REAL minRotationSpeed = 0.0f;
	REAL maxRotationSpeed = 0.0f;

	// Read class GUID
	if(RuReadGUID(stream, classGUID) == FALSE)
	{
		return FALSE;
	}

	// Read version
	INT32 version = 1;
	stream->Read(&version, sizeof(INT32));

	switch(version)
	{
		case 1:
			{
				// Read blend settings
				stream->Read(&m_blendType, sizeof(INT32));

				// Read length of texture name including NULL, followed by the name
				INT32 len = 0;
				stream->Read(&len, sizeof(INT32));
				m_spriteTextureName = ruNEW char [len];
				stream->Read(m_spriteTextureName, len);

				// Read texture settings
				stream->Read(&m_spriteSubTextureWidth, sizeof(INT32));
				stream->Read(&m_spriteSubTextureHeight, sizeof(INT32));
				stream->Read(&m_frameRate, sizeof(REAL));
				stream->Read(&m_numFrames, sizeof(INT32));

				// Read color settings
				stream->Read(&startColor, sizeof(DWORD));
				stream->Read(&endColor, sizeof(DWORD));
				stream->Read(&startScale, sizeof(REAL));
				stream->Read(&endScale, sizeof(REAL));

				// If particle emitter is available, serialize particle emitter
				stream->Read(&emitterAvailable, sizeof(BOOL));
			}

			break;

		case 2:
			{
				// Read blend settings
				stream->Read(&m_blendType, sizeof(INT32));

				// Read length of texture name including NULL, followed by the name
				INT32 len = 0;
				stream->Read(&len, sizeof(INT32));
				m_spriteTextureName = ruNEW char [len];
				stream->Read(m_spriteTextureName, len);

				// Read texture settings
				stream->Read(&m_spriteSubTextureWidth, sizeof(INT32));
				stream->Read(&m_spriteSubTextureHeight, sizeof(INT32));
				stream->Read(&m_frameRate, sizeof(REAL));
				stream->Read(&m_numFrames, sizeof(INT32));

				// Read color settings
				stream->Read(&startColor, sizeof(DWORD));
				stream->Read(&endColor, sizeof(DWORD));
				stream->Read(&startScale, sizeof(REAL));
				stream->Read(&endScale, sizeof(REAL));
				stream->Read(&minRotationSpeed, sizeof(REAL));
				stream->Read(&maxRotationSpeed, sizeof(REAL));

				// If particle emitter is available, serialize particle emitter
				stream->Read(&emitterAvailable, sizeof(BOOL));
			}

			break;

		case 3:
			{
				// Read orientation
				stream->Read(&m_orientation, sizeof(RuBillboardType));

				// Read billboard settings
				stream->Read(&m_billboardType, sizeof(RuBillboardType));

				// Read blend settings
				stream->Read(&m_blendType, sizeof(INT32));

				// Read length of texture name including NULL, followed by the name
				INT32 len = 0;
				stream->Read(&len, sizeof(INT32));
				m_spriteTextureName = ruNEW char [len];
				stream->Read(m_spriteTextureName, len);

				// Read texture settings
				stream->Read(&m_spriteSubTextureWidth, sizeof(INT32));
				stream->Read(&m_spriteSubTextureHeight, sizeof(INT32));
				stream->Read(&m_frameRate, sizeof(REAL));
				stream->Read(&m_numFrames, sizeof(INT32));

				// Read color settings
				stream->Read(&startColor, sizeof(DWORD));
				stream->Read(&endColor, sizeof(DWORD));
				stream->Read(&startScale, sizeof(REAL));
				stream->Read(&endScale, sizeof(REAL));
				stream->Read(&minRotationSpeed, sizeof(REAL));
				stream->Read(&maxRotationSpeed, sizeof(REAL));

				// If particle emitter is available, serialize particle emitter
				stream->Read(&emitterAvailable, sizeof(BOOL));
			}

			break;

		case 4:
		case 5:	// NOTE: Versions 4 and 5 are identical except rotation speed is now interpreted as degrees instead of radians
			{
				// Read orientation
				stream->Read(&m_orientation, sizeof(RuBillboardType));

				// Read billboard settings
				stream->Read(&m_billboardType, sizeof(RuBillboardType));

				// Read blend settings
				stream->Read(&m_blendType, sizeof(INT32));

				// Read length of texture name including NULL, followed by the name
				INT32 len = 0;
				stream->Read(&len, sizeof(INT32));
				m_spriteTextureName = ruNEW char [len];
				stream->Read(m_spriteTextureName, len);

				// Read texture settings
				stream->Read(&m_spriteSubTextureWidth, sizeof(INT32));
				stream->Read(&m_spriteSubTextureHeight, sizeof(INT32));
				stream->Read(&m_frameRate, sizeof(REAL));
				stream->Read(&m_numFrames, sizeof(INT32));

				// Read other settings
				stream->Read(&m_randomRotation, sizeof(BOOL));

				// Serialize tracks
				m_track_Color.SerializeFrom(stream);
				m_track_Alpha.SerializeFrom(stream);
				m_track_Scale.SerializeFrom(stream);
				m_track_RotationSpeed.SerializeFrom(stream);

				// If particle emitter is available, serialize particle emitter
				stream->Read(&emitterAvailable, sizeof(BOOL));
			}

			break;
	}

	if(version < 4)
	{
		m_track_Color.SetKeyByIndex(0, CRuColor4(startColor));
		m_track_Color.SetKeyByIndex(1, CRuColor4(endColor));

		m_track_Alpha.SetKeyByIndex(0, CRuColor4(startColor).m_a);
		m_track_Alpha.SetKeyByIndex(1, CRuColor4(endColor).m_a);

		m_track_Scale.SetKeyByIndex(0, startScale);
		m_track_Scale.SetKeyByIndex(1, endScale);

		m_track_RotationSpeed.SetKeyByIndex(0, minRotationSpeed);
		m_track_RotationSpeed.SetKeyByIndex(1, minRotationSpeed);
	}

	if(version < 5)
	{
		// Convert PI to degrees
		for(INT32 i = 0; i < m_track_RotationSpeed.GetNumKeys(); ++i)
		{
			REAL rotation;
			m_track_RotationSpeed.GetKeyByIndex(i, &rotation);
			rotation = rotation * 360.0f / (2.0f * ruPI);
			m_track_RotationSpeed.SetKeyByIndex(i, rotation);
		}
	}

	if(emitterAvailable)
	{
		IRuObject *object = g_ruObjectFactory.CreateObject(stream);
		if(object && object->GetType().IsSubClassOf(IRuEntity_Particle_Emitter::Type))
		{
			m_emitter = static_cast<IRuEntity_Particle_Emitter *>(object);

			// Disable automatic cloning and serialization
			m_emitter->SetOption(ruENTITYOPTION_NOCLONE, TRUE);
			m_emitter->SetOption(ruENTITYOPTION_NOSERIALIZE, TRUE);

			this->AddChild(m_emitter);
		}
		else
		{
			ruSAFE_RELEASE(object);
		}
	}

	// Reload texture
	ruSAFE_RELEASE(m_spriteTexture);
	m_spriteTexture = g_ruResourceManager->LoadTexture(m_spriteTextureName);

	// Reset orientation
	SetOrientation(m_orientation);

	return TRUE;
}

BOOL CRuEntity_Particle_Visualizer_Sprite::SerializeTo(IRuStream *stream)
{
	// Write class GUID
	stream->Write(GetClassGUID().Data(), GetClassGUID().Size());

	// Write version
	INT32 version = 5;
	stream->Write(&version, sizeof(INT32));

	// Write orientation
	stream->Write(&m_orientation, sizeof(RuBillboardType));

	// Write billboard settings
	stream->Write(&m_billboardType, sizeof(RuBillboardType));

	// Write blend settings
	stream->Write(&m_blendType, sizeof(INT32));

	// Write length of texture name including NULL, followed by the name
	if(m_spriteTextureName)
	{
		INT32 len = strlen(m_spriteTextureName) + 1;
		stream->Write(&len, sizeof(INT32));
		stream->Write(m_spriteTextureName, len);
	}
	else
	{
		INT32 len = 1;
		stream->Write(&len, sizeof(INT32));
		stream->Write("", len);
	}

	// Write texture settings
	stream->Write(&m_spriteSubTextureWidth, sizeof(INT32));
	stream->Write(&m_spriteSubTextureHeight, sizeof(INT32));
	stream->Write(&m_frameRate, sizeof(REAL));
	stream->Write(&m_numFrames, sizeof(INT32));

	// Write other settings
	stream->Write(&m_randomRotation, sizeof(BOOL));

	// Serialize tracks
	m_track_Color.SerializeTo(stream);
	m_track_Alpha.SerializeTo(stream);
	m_track_Scale.SerializeTo(stream);
	m_track_RotationSpeed.SerializeTo(stream);

	// If particle emitter is available, serialize particle emitter
	if(m_emitter)
	{
		BOOL emitterAvailable = TRUE;
		stream->Write(&emitterAvailable, sizeof(BOOL));
		m_emitter->SerializeTo(stream);
	}
	else
	{
		BOOL emitterAvailable = FALSE;
		stream->Write(&emitterAvailable, sizeof(BOOL));
	}

	return TRUE;
}

BOOL CRuEntity_Particle_Visualizer_Sprite::ResetStates()
{
	return IRuEntity_Particle_Visualizer::ResetStates();
}

BOOL CRuEntity_Particle_Visualizer_Sprite::Update(REAL elapsedTime)
{
	// Chain update to base class
	BOOL retVal = IRuEntity_Particle_Visualizer::Update(elapsedTime);

	// Rebuild particle batches
	BuildBatches();

	if(m_emitter->GetFrameType() == ruPRTFRAMETYPE_WORLD)
	{
		m_model->SetOption(ruENTITYOPTION_WTOVERRIDE, TRUE);
	}
	else
	{
		m_model->SetOption(ruENTITYOPTION_WTOVERRIDE, FALSE);
	}

	return retVal;
}

BOOL CRuEntity_Particle_Visualizer_Sprite::QueryCollision(CRuCollisionQuery *colQuery)
{
	// Chain collision query to the emitter
	if(m_emitter)
	{
		return m_emitter->QueryCollision(colQuery);
	}

	return TRUE;
}

INT32 CRuEntity_Particle_Visualizer_Sprite::GetNumEntities()
{
	return 1;
}

CRuEntity *CRuEntity_Particle_Visualizer_Sprite::GetEntity(INT32 index)
{
	return m_model;
}

IRuEntity_Particle_Emitter *CRuEntity_Particle_Visualizer_Sprite::GetEmitter()
{
	return m_emitter;
}

BOOL CRuEntity_Particle_Visualizer_Sprite::SetEmitter(IRuEntity_Particle_Emitter *emitter)
{
	if(emitter)
	{
		this->RemoveChild(m_emitter);

		// Release original emitter and store reference to new emitter
		ruSAFE_RELEASE(m_emitter);
		m_emitter = emitter;
		m_emitter->AddRef();

		// Disable automatic cloning and serialization
		m_emitter->SetOption(ruENTITYOPTION_NOCLONE, TRUE);
		m_emitter->SetOption(ruENTITYOPTION_NOSERIALIZE, TRUE);

		this->AddChild(m_emitter);

		return TRUE;
	}

	return FALSE;
}

BOOL CRuEntity_Particle_Visualizer_Sprite::HandleEvent(CRuEntity *entity, INT32 eventID, INT32 eventParameter0, INT32 eventParameter1, const char *eventString, REAL eventDuration, void *userData)
{
	// Handle event
	switch(eventID)
	{
		case ruENTITYEVENT_BROADCAST_CAMERA:
			{
				// Cast parameter 0 as the primary camera
				CRuCamera *primaryCamera = reinterpret_cast<CRuCamera *>(eventParameter0);

				// Store aggregated transform matrix
				m_aggregateMatrix = GetWorldTransform() * primaryCamera->GetCameraTransform();
			}

			break;
	}

	return FALSE;
}

RuOrthogonalAxis CRuEntity_Particle_Visualizer_Sprite::GetOrientation() const
{
	return m_orientation;
}

void CRuEntity_Particle_Visualizer_Sprite::SetOrientation(RuOrthogonalAxis orientation)
{
	m_orientation = orientation;

	BuildMesh();
}

RuBillboardType CRuEntity_Particle_Visualizer_Sprite::GetBillboardType() const
{
	return m_billboardType;
}

void CRuEntity_Particle_Visualizer_Sprite::SetBillboardType(RuBillboardType billboardType)
{
	m_billboardType = billboardType;
}

RuAlphaMode CRuEntity_Particle_Visualizer_Sprite::GetBlendType() const
{
	return m_blendType;
}

void CRuEntity_Particle_Visualizer_Sprite::SetBlendType(RuAlphaMode blendType)
{
	m_blendType = blendType;
}

const char *CRuEntity_Particle_Visualizer_Sprite::GetTextureName() const
{
	if(m_spriteTextureName)
	{
		return m_spriteTextureName;
	}

	return "";
}

void CRuEntity_Particle_Visualizer_Sprite::SetTextureName(const char *textureName)
{
	if(textureName)
	{
		if(m_spriteTextureName)
		{
			delete[] m_spriteTextureName;
		}

		m_spriteTextureName = ruNEW char [strlen(textureName) + 1];
		strcpy(m_spriteTextureName, textureName);

		// Reload texture
		ruSAFE_RELEASE(m_spriteTexture);
		m_spriteTexture = g_ruResourceManager->LoadTexture(m_spriteTextureName);
	}
}

INT32 CRuEntity_Particle_Visualizer_Sprite::GetSubTextureWidth() const
{
	return m_spriteSubTextureWidth;
}

void CRuEntity_Particle_Visualizer_Sprite::SetSubTextureWidth(INT32 width)
{
	if(width > 0)
	{
		m_spriteSubTextureWidth = width;

		// Force rebuild of the mesh
		BuildMesh();
	}
}

INT32 CRuEntity_Particle_Visualizer_Sprite::GetSubTextureHeight() const
{
	return m_spriteSubTextureHeight;
}

void CRuEntity_Particle_Visualizer_Sprite::SetSubTextureHeight(INT32 height)
{
	if(height > 0)
	{
		m_spriteSubTextureHeight = height;

		// Force rebuild of the mesh
		BuildMesh();
	}
}

REAL CRuEntity_Particle_Visualizer_Sprite::GetFrameRate() const
{
	return m_frameRate;
}

void CRuEntity_Particle_Visualizer_Sprite::SetFrameRate(REAL frameRate)
{
	if(frameRate > 0.0f)
	{
		m_frameRate = frameRate;

		// Force rebuild of the mesh
		BuildMesh();
	}
}

INT32 CRuEntity_Particle_Visualizer_Sprite::GetNumFrames() const
{
	return m_numFrames;
}

void CRuEntity_Particle_Visualizer_Sprite::SetNumFrames(INT32 numFrames)
{
	if(numFrames > 0)
	{
		m_numFrames = numFrames;
	}
}

BOOL CRuEntity_Particle_Visualizer_Sprite::GetOrientToDirectionOfTravel() const
{
	return m_orientToDirectionOfTravel;
}

void CRuEntity_Particle_Visualizer_Sprite::SetOrientToDirectionOfTravel(BOOL orientToDirectionOfTravel)
{
	m_orientToDirectionOfTravel = orientToDirectionOfTravel;
}

BOOL CRuEntity_Particle_Visualizer_Sprite::GetRandomRotation() const
{
	return m_randomRotation;
}

void CRuEntity_Particle_Visualizer_Sprite::SetRandomRotation(BOOL randomRotation)
{
	m_randomRotation = randomRotation;
}

const CRuVector3 &CRuEntity_Particle_Visualizer_Sprite::GetQuadOffset() const
{
	return m_quadOffset;
}

void CRuEntity_Particle_Visualizer_Sprite::SetQuadOffset(const CRuVector3 &quadOffset)
{
	m_quadOffset = quadOffset;

	// Force rebuild of mesh
	BuildMesh();
}

const CRuVector3 &CRuEntity_Particle_Visualizer_Sprite::GetQuadScale() const
{
	return m_quadScale;
}

void CRuEntity_Particle_Visualizer_Sprite::SetQuadScale(const CRuVector3 &quadScale)
{
	m_quadScale = quadScale;

	// Force rebuild of mesh
	BuildMesh();
}

CRuAnimTrack<CRuColor4> &CRuEntity_Particle_Visualizer_Sprite::GetTrack_Color()
{
	return m_track_Color;
}

CRuAnimTrack<REAL> &CRuEntity_Particle_Visualizer_Sprite::GetTrack_Alpha()
{
	return m_track_Alpha;
}

CRuAnimTrack<REAL> &CRuEntity_Particle_Visualizer_Sprite::GetTrack_Scale()
{
	return m_track_Scale;
}

CRuAnimTrack<REAL> &CRuEntity_Particle_Visualizer_Sprite::GetTrack_RotationSpeed()
{
	return m_track_RotationSpeed;
}

DWORD CRuEntity_Particle_Visualizer_Sprite::GetStartColor() const
{
	CRuColor4 color;
	m_track_Color.GetKeyByIndex(0, &color);
	return color.GetARGB();
}

void CRuEntity_Particle_Visualizer_Sprite::SetStartColor(DWORD color)
{
	m_track_Color.SetKeyByIndex(0, CRuColor4(color));
}

DWORD CRuEntity_Particle_Visualizer_Sprite::GetEndColor() const
{
	CRuColor4 color;
	m_track_Color.GetKeyByIndex(m_track_Color.GetNumKeys() - 1, &color);
	return color.GetARGB();
}

void CRuEntity_Particle_Visualizer_Sprite::SetEndColor(DWORD color)
{
	m_track_Color.SetKeyByIndex(m_track_Color.GetNumKeys() - 1, CRuColor4(color));
}

REAL CRuEntity_Particle_Visualizer_Sprite::GetStartAlpha() const
{
	REAL alpha;
	m_track_Alpha.GetKeyByIndex(0, &alpha);
	return alpha;
}

void CRuEntity_Particle_Visualizer_Sprite::SetStartAlpha(REAL alpha)
{
	m_track_Alpha.SetKeyByIndex(0, alpha);
}

REAL CRuEntity_Particle_Visualizer_Sprite::GetEndAlpha() const
{
	REAL alpha;
	m_track_Alpha.GetKeyByIndex(m_track_Alpha.GetNumKeys() - 1, &alpha);
	return alpha;
}

void CRuEntity_Particle_Visualizer_Sprite::SetEndAlpha(REAL alpha)
{
	m_track_Alpha.SetKeyByIndex(m_track_Alpha.GetNumKeys() - 1, alpha);
}

REAL CRuEntity_Particle_Visualizer_Sprite::GetStartScale() const
{
	REAL scale;
	m_track_Scale.GetKeyByIndex(0, &scale);
	return scale;
}

void CRuEntity_Particle_Visualizer_Sprite::SetStartScale(REAL scale)
{
	m_track_Scale.SetKeyByIndex(0, scale);
}

REAL CRuEntity_Particle_Visualizer_Sprite::GetEndScale() const
{
	REAL scale;
	m_track_Scale.GetKeyByIndex(m_track_Scale.GetNumKeys() - 1, &scale);
	return scale;
}

void CRuEntity_Particle_Visualizer_Sprite::SetEndScale(REAL scale)
{
	m_track_Scale.SetKeyByIndex(m_track_Scale.GetNumKeys() - 1, scale);
}

REAL CRuEntity_Particle_Visualizer_Sprite::GetRotationSpeed() const
{
	REAL speed;
	m_track_RotationSpeed.GetKeyByIndex(0, &speed);
	return speed;
}

void CRuEntity_Particle_Visualizer_Sprite::SetRotationSpeed(REAL speed)
{
	m_track_RotationSpeed.SetKeyByIndex(0, speed);
	m_track_RotationSpeed.SetKeyByIndex(m_track_RotationSpeed.GetNumKeys() - 1, speed);
}

void CRuEntity_Particle_Visualizer_Sprite::BuildMesh()
{
	REAL quadSizeX = 0.5f * m_quadScale.m_x;
	REAL quadSizeY = 0.5f * m_quadScale.m_y;
	REAL aspectRatio = m_spriteSubTextureWidth > 0.0f ? (REAL) m_spriteSubTextureHeight / m_spriteSubTextureWidth : 1.0f;

	aspectRatio = (REAL) m_spriteSubTextureHeight / m_spriteSubTextureWidth;

	switch(m_orientation)
	{
		case ruAXIS_X:
			{
			}

			break;

		case ruAXIS_Y:
			{
				// Construct particle mesh geometry
				CRuVector3 *position = static_cast<CRuMorphTarget *>(m_mesh->GetMorphTarget(0))->GetPosition();
				DWORD *diffuse = static_cast<CRuMorphTarget *>(m_mesh->GetMorphTarget(0))->GetDiffuse();
				UINT16 *indices = m_mesh->GetIndices();

				for(INT32 i = 0, vIdx = 0, fIdx = 0; i < m_maxParticlesPerBatch; ++i)
				{
					// v0
					diffuse[vIdx] = (i << 24) | (0x00 << 16) | (0x00 << 8);
					position[vIdx].Set(quadSizeX + (quadSizeX * m_quadOffset.m_x), 0.0f, (-quadSizeY + (quadSizeY * m_quadOffset.m_y)) * aspectRatio);
					++vIdx;

					// v1
					diffuse[vIdx] = (i << 24) | (0x00 << 16) | (0xFF << 8);
					position[vIdx].Set(quadSizeX + (quadSizeX * m_quadOffset.m_x), 0.0f, (quadSizeY + (quadSizeY * m_quadOffset.m_y)) * aspectRatio);
					++vIdx;

					// v2
					diffuse[vIdx] = (i << 24) | (0xFF << 16) | (0xFF << 8);
					position[vIdx].Set(-quadSizeX + (quadSizeX * m_quadOffset.m_x), 0.0f, (quadSizeY + (quadSizeY * m_quadOffset.m_y)) * aspectRatio);
					++vIdx;

					// v3
					diffuse[vIdx] = (i << 24) | (0xFF << 16) | (0x00 << 8);
					position[vIdx].Set(-quadSizeX + (quadSizeX * m_quadOffset.m_x), 0.0f, (-quadSizeY + (quadSizeY * m_quadOffset.m_y)) * aspectRatio);
					++vIdx;

					INT32 vertexOffset = i * 4;

					indices[fIdx++] = vertexOffset + 0;
					indices[fIdx++] = vertexOffset + 2;
					indices[fIdx++] = vertexOffset + 1;

					indices[fIdx++] = vertexOffset + 0;
					indices[fIdx++] = vertexOffset + 3;
					indices[fIdx++] = vertexOffset + 2;
				}

				m_mesh->IncrementMeshIteration();
				m_mesh->IncrementTopologyIteration();
			}

			break;

		case ruAXIS_Z:
			{
				// Construct particle mesh geometry
				CRuVector3 *position = static_cast<CRuMorphTarget *>(m_mesh->GetMorphTarget(0))->GetPosition();
				DWORD *diffuse = static_cast<CRuMorphTarget *>(m_mesh->GetMorphTarget(0))->GetDiffuse();
				UINT16 *indices = m_mesh->GetIndices();

				for(INT32 i = 0, vIdx = 0, fIdx = 0; i < m_maxParticlesPerBatch; ++i)
				{
					// v0
					diffuse[vIdx] = (i << 24) | (0x00 << 16) | (0x00 << 8);
					position[vIdx].Set(quadSizeX + (quadSizeX * m_quadOffset.m_x), (quadSizeY + (quadSizeY * m_quadOffset.m_y)) * aspectRatio, 0.0f);
					++vIdx;

					// v1
					diffuse[vIdx] = (i << 24) | (0x00 << 16) | (0xFF << 8);
					position[vIdx].Set(quadSizeX + (quadSizeX * m_quadOffset.m_x), (-quadSizeY + (quadSizeY * m_quadOffset.m_y)) * aspectRatio, 0.0f);
					++vIdx;

					// v2
					diffuse[vIdx] = (i << 24) | (0xFF << 16) | (0xFF << 8);
					position[vIdx].Set(-quadSizeX + (quadSizeX * m_quadOffset.m_x), (-quadSizeY + (quadSizeY * m_quadOffset.m_y)) * aspectRatio, 0.0f);
					++vIdx;

					// v3
					diffuse[vIdx] = (i << 24) | (0xFF << 16) | (0x00 << 8);
					position[vIdx].Set(-quadSizeX + (quadSizeX * m_quadOffset.m_x), (quadSizeY + (quadSizeY * m_quadOffset.m_y)) * aspectRatio, 0.0f);
					++vIdx;

					INT32 vertexOffset = i * 4;

					indices[fIdx++] = vertexOffset + 0;
					indices[fIdx++] = vertexOffset + 2;
					indices[fIdx++] = vertexOffset + 1;

					indices[fIdx++] = vertexOffset + 0;
					indices[fIdx++] = vertexOffset + 3;
					indices[fIdx++] = vertexOffset + 2;
				}

				m_mesh->IncrementMeshIteration();
				m_mesh->IncrementTopologyIteration();
			}

			break;
	}
}

void CRuEntity_Particle_Visualizer_Sprite::BuildBatches()
{
	// Abort if emitter is not available
	if(m_emitter == NULL)
		return;

	// Clear existing batches
	ClearBatches();

	// Decompose orientation transform
	const CRuMatrix4x4 &worldTransform = this->GetWorldTransform();
	CRuVector3 wScale, wShear, wRotation, wTranslation;
	worldTransform.GetDecomposition(wScale, wShear, wRotation, wTranslation);

	// Calculate frame period
	REAL framePeriod = 1.0f / m_frameRate;

	// Get particle list from the emitter
	CRuArrayList<CRuParticleDesc> *particles = m_emitter->GetParticleDescriptors();

	CRuSphere meshBounds;;

	// Iterate through all particles and build batches
	BatchDesc *batch = NULL;
	INT32 particlesThisBatch = 0;
	INT32 currentTextureWidth = 0;
	INT32 currentTextureHeight = 0;
	for(INT32 i = 0, j = 24; i < particles->Count(); ++i)
	{
		// Store reference to the particle
		const CRuParticleDesc &particle = (*particles)[i];

		// Skip inactive particles
		if(particle.m_active == FALSE)
			continue;

		// Make sure run-time particles list is at least as large as the emitter particle list
		while(i >= m_particles.Count())
		{
			m_particles.Add(ParticleDesc());
		}

		// Detect if there is a need for a new batch
		if(batch == NULL || particlesThisBatch >= m_maxParticlesPerBatch)
		{
			// Set number of particles for the previous batch if applicable
			if(batch)
			{
				// Set rendering parameters
				batch->m_material->GetParameterBlock()->SetParameter(ruPARAMTYPE_GEOINSTANCE_S_COUNT, particlesThisBatch);
				batch->m_material->GetParameterBlock()->SetParameter(ruPARAMTYPE_GLOBAL_ALPHAMODE, m_blendType);

				// Setup rendering batch
				batch->m_mesh->SetProxiedMesh(m_mesh, m_mesh->AddBatch(0, particlesThisBatch * 2));
			}

			// Get a free batch descriptor
			if(m_freeBatchDescStack.IsEmpty())
			{
				batch = ruNEW BatchDesc(m_maxParticlesPerBatch);
			}
			else
			{
				batch = m_freeBatchDescStack.Pop();
			}

			// Select texture
			if(m_spriteTexture)
			{
				batch->m_material->SetTexture(ruTEXCHANNEL_DIFFUSEMAP, m_spriteTexture);

				currentTextureWidth = m_spriteTexture->GetLevelWidth(0);
				currentTextureHeight = m_spriteTexture->GetLevelHeight(0);

				// Setup header constants
				batch->m_vsConstants[0] = (REAL) m_spriteSubTextureWidth / m_spriteTexture->GetLevelWidth(0);
				batch->m_vsConstants[1] = (REAL) m_spriteSubTextureHeight / m_spriteTexture->GetLevelHeight(0);
				batch->m_vsConstants[2] = 2.0f;
				batch->m_vsConstants[3] = 2.0f;
			}
			else
			{
				batch->m_material->SetTexture(ruTEXCHANNEL_DIFFUSEMAP, NULL);

				currentTextureWidth = 16;
				currentTextureHeight = 16;

				// Setup header constants
				batch->m_vsConstants[0] = 0.0f;
				batch->m_vsConstants[1] = 0.0f;
				batch->m_vsConstants[2] = 0.0f;
				batch->m_vsConstants[3] = 0.0f;
			}

			batch->m_vsConstants[4] = 256.0f;						// diffuse color scale factor - so we can get a proper integer index
			batch->m_vsConstants[5] = 3.0f;							// 3 constant rows per instance dataset
			batch->m_vsConstants[6] = 37.0f;						// Instance dataset begins at c37
			batch->m_vsConstants[7] = 1.0f / 480.0f;				// reciprocal fade distance

			batch->m_vsConstants[8] = 0.0f;							// normal x
			batch->m_vsConstants[9] = 1.0f;							// normal y
			batch->m_vsConstants[10] = 0.0f;						// normal z
			batch->m_vsConstants[11] = 0.0f;						// unused

			batch->m_vsConstants[12] = 1.0f;
			batch->m_vsConstants[13] = 1.0f;
			batch->m_vsConstants[14] = 1.0f;
			batch->m_vsConstants[15] = 1.0f;

			batch->m_vsConstants[16] = 1.0f;
			batch->m_vsConstants[17] = 1.0f;
			batch->m_vsConstants[18] = 1.0f;
			batch->m_vsConstants[19] = 1.0f;

			batch->m_vsConstants[20] =  1.00000000f;
			batch->m_vsConstants[21] = -0.16666667f;
			batch->m_vsConstants[22] =  0.00833333f;
			batch->m_vsConstants[23] = -0.00019841f;

			// Reset constant index
			particlesThisBatch = 0;
			j = 24;

			// Add batch to batch list
			m_batches.Add(batch);
		}

		REAL t = particle.m_age / particle.m_extinction;

		// If particle is a brand new one, assign it a rotational speed
		if(m_particles[i].m_particleIteration != particle.m_particleIteration)
		{
			REAL rotationSpeed = m_track_RotationSpeed.GetDiscreteKey(t * m_track_RotationSpeed.GetTrackLength());

			m_particles[i].m_particleIteration = particle.m_particleIteration;

			if(m_randomRotation)
			{
				m_particles[i].m_rotationDirection = g_mtRand.GenerateINT32() % 2 ? 1 : -1;
			}
			else
			{
				m_particles[i].m_rotationDirection = 1;
			}
		}

		INT32 subTexPerRow = max(1, currentTextureWidth / m_spriteSubTextureWidth);
		INT32 textureIndex = static_cast<INT32>(particle.m_age * m_frameRate) % m_numFrames;

		REAL scale = m_track_Scale.GetInterpolatedKey(t * m_track_Scale.GetTrackLength());
		REAL alpha = m_track_Alpha.GetInterpolatedKey(t * m_track_Alpha.GetTrackLength());
		CRuColor4 color = m_track_Color.GetInterpolatedKey(t * m_track_Color.GetTrackLength());
		REAL rotationSpeed = m_track_RotationSpeed.GetInterpolatedKey(t * m_track_RotationSpeed.GetTrackLength()) * 2.0f * ruPI / 360.0f;

		REAL sinValue;
		REAL cosValue;

		if(m_orientToDirectionOfTravel)
		{
			CRuVector3 projectedVelocity = particle.m_direction;

			m_aggregateMatrix.TransformVectorProjected(projectedVelocity);

			REAL angle = 0.0f;

			projectedVelocity.m_z = 0.0f;

			if(projectedVelocity.SquaredMagnitude() > ruEPSILON)
			{
				projectedVelocity.Normalize();

				angle = acos(DotProduct(CRuVector3(0.0f, 1.0f, 0.0f), projectedVelocity));
				REAL tripleProduct = TripleProduct_Scalar(CRuVector3(0.0f, 1.0f, 0.0f), projectedVelocity, CRuVector3(0.0f, 0.0f, -1.0f));

				if(tripleProduct < 0.0f)
				{
					angle = ruPI * 2.0f - angle;
				}
			}

			sinValue = sin(angle);
			cosValue = cos(angle);
		}
		else
		{
			sinValue = sin(particle.m_age * m_particles[i].m_rotationDirection * rotationSpeed + particle.m_orientation.m_x);
			cosValue = cos(particle.m_age * m_particles[i].m_rotationDirection * rotationSpeed + particle.m_orientation.m_x);
		}

		// Setup constant data for this particle
		batch->m_vsConstants[j++] = particle.m_position.x;												// position x
		batch->m_vsConstants[j++] = particle.m_position.y;												// position y
		batch->m_vsConstants[j++] = particle.m_position.z;												// position z
		batch->m_vsConstants[j++] = particle.m_scale * scale * wScale.x;								// scale

		batch->m_vsConstants[j++] = static_cast<REAL>(textureIndex % subTexPerRow);						// u index
		batch->m_vsConstants[j++] = static_cast<REAL>(textureIndex / subTexPerRow);						// v index
		batch->m_vsConstants[j++] = sinValue;															// sin(phi)
		batch->m_vsConstants[j++] = cosValue;															// cos(phi)

		batch->m_vsConstants[j++] = color.m_r;															// red
		batch->m_vsConstants[j++] = color.m_g;															// green
		batch->m_vsConstants[j++] = color.m_b;															// blue
		batch->m_vsConstants[j++] = alpha;																// alpha

		meshBounds.Merge(particle.m_position, 0.0f);

		// Increment particle count
		++particlesThisBatch;
	}

	if(batch)
	{
		batch->m_material->GetParameterBlock()->SetParameter(ruPARAMTYPE_GEOINSTANCE_S_COUNT, particlesThisBatch);
		batch->m_material->GetParameterBlock()->SetParameter(ruPARAMTYPE_GLOBAL_ALPHAMODE, m_blendType);

		// Setup rendering batch
		batch->m_mesh->SetProxiedMesh(m_mesh, m_mesh->AddBatch(0, particlesThisBatch * 2));
	}

	// Set mesh bounds to equal that of the parent entity's
	m_mesh->SetBounds(meshBounds);

	// Construct model from batches
	m_model->RemoveAllSubModels();
	for(INT32 i = 0; i < m_batches.Count(); ++i)
	{
		m_model->AddSubModel(m_batches[i]->m_mesh, m_batches[i]->m_material);
	}

	// Set billboard parameter
	m_model->GetParameterBlock()->SetParameter(ruPARAMTYPE_FX_BILLBOARD, static_cast<DWORD>(m_billboardType));

	m_model->GetParameterBlock()->SetParameter(ruPARAMTYPE_DWORD_0, static_cast<DWORD>(m_orientation));
}

void CRuEntity_Particle_Visualizer_Sprite::ClearBatches()
{
	// Push all used batches onto stack
	for(INT32 i = 0; i < m_batches.Count(); ++i)
	{
		m_freeBatchDescStack.Push(m_batches[i]);
	}

	// Clear batches
	m_batches.Clear();

	// Clear batches
	m_mesh->ClearBatches();
}

// ************************************************************************************************************************************************************

#pragma managed
