#include "gosFXHeaders.hpp"
#include <MLR\MLRIndexedTriangleCloud.hpp>

//==========================================================================//
// File:	 gosFX_Tube.cpp										//
// Project:  gosFX					                                        //
// Contents: Base gosFX::Tube Component									//
//--------------------------------------------------------------------------//
// Date      Who    Modification                                            //
// 09/28/98  JTR    Created													//
//																			//
//--------------------------------------------------------------------------//
// Copyright (C) 1997-1998, Virtual World Entertainment, Inc.               //
// All Rights reserved worldwide                                            //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL              //
//==========================================================================//
//
//############################################################################
//########################  gosFX::Tube__Specification  #############################
//############################################################################

//------------------------------------------------------------------------------
//
gosFX::Tube__Specification::Tube__Specification(
	Stuff::MemoryStream *stream,
	int gfx_version
):
	Effect__Specification(TubeClassID, stream, gfx_version)
{
	Check_Pointer(this);
	Check_Object(stream);

	//
	//-------------------
	// Load in the curves
	//-------------------
	//
	m_profilesPerSecond.Load(stream, gfx_version);
	m_pLifeSpan.Load(stream, gfx_version);
	m_emitterSizeX.Load(stream, gfx_version);
	m_emitterSizeY.Load(stream, gfx_version);
	m_emitterSizeZ.Load(stream, gfx_version);
	m_minimumDeviation.Load(stream, gfx_version);
	m_maximumDeviation.Load(stream, gfx_version);
	m_pDisplacement.Load(stream, gfx_version);
	if (gfx_version < 12)
	{
		m_pScale.m_ageCurve.SetCurve(1.0f);
		m_pScale.m_seeded = false;
		m_pScale.m_seedCurve.SetCurve(1.0f);
	}
	else
		m_pScale.Load(stream, gfx_version);
	m_pRed.Load(stream, gfx_version);
	m_pGreen.Load(stream, gfx_version);
	m_pBlue.Load(stream, gfx_version);
	m_pAlpha.Load(stream, gfx_version);
	m_pUOffset.Load(stream, gfx_version);
	m_pVOffset.Load(stream, gfx_version);
	m_pUSize.Load(stream, gfx_version);
	m_pVSize.Load(stream, gfx_version);

	int type;
	*stream >> m_maxProfileCount >> type;
	if (gfx_version >= 22)
		*stream >> m_randomDirections;
	else
		m_randomDirections = false;
	*stream >> m_insideOut;
	if (gfx_version >= 24)
		*stream >> m_fadeFirstToBlack >> m_fadeFirstToWhite;
	else
		m_fadeFirstToBlack = m_fadeFirstToWhite = false;
	m_profileType = static_cast<ProfileType>(type);
	CalculateUBias(true);
	BuildTemplate();
}

//------------------------------------------------------------------------------
//
gosFX::Tube__Specification::Tube__Specification():
	Effect__Specification(TubeClassID)
{
	m_maxProfileCount = 0;
	m_profileType = e_Ribbon;
	m_insideOut = false;
	m_randomDirections = false;
	m_UBias = 0.0f;
	m_fadeFirstToWhite = false;
	m_fadeFirstToBlack = false;
	Check_Pointer(this);
}

//------------------------------------------------------------------------------
//
gosFX::Tube__Specification::~Tube__Specification()
{
	Check_Object(this);
}

//------------------------------------------------------------------------------
//
gosFX::Tube__Specification*
	gosFX::Tube__Specification::Make(
		Stuff::MemoryStream *stream,
		int gfx_version
	)
{
	Check_Object(stream);

	gos_PushCurrentHeap(Heap);
	Tube__Specification *spec =
		new gosFX::Tube__Specification(stream, gfx_version);
	gos_PopCurrentHeap();

	return spec;
}

//------------------------------------------------------------------------------
//
void
	gosFX::Tube__Specification::Save(Stuff::MemoryStream *stream)
{
	Check_Object(this);
	Check_Object(stream);
	BaseClass::Save(stream);

	//
	//----------------
	// Save our curves
	//----------------
	//
	m_profilesPerSecond.Save(stream);
	m_pLifeSpan.Save(stream);
	m_emitterSizeX.Save(stream);
	m_emitterSizeY.Save(stream);
	m_emitterSizeZ.Save(stream);
	m_minimumDeviation.Save(stream);
	m_maximumDeviation.Save(stream);
	m_pDisplacement.Save(stream);
	m_pScale.Save(stream);
	m_pRed.Save(stream);
	m_pGreen.Save(stream);
	m_pBlue.Save(stream);
	m_pAlpha.Save(stream);
	m_pUOffset.Save(stream);
	m_pVOffset.Save(stream);
	m_pUSize.Save(stream);
	m_pVSize.Save(stream);

	*stream << m_maxProfileCount << static_cast<int>(m_profileType);
	*stream << m_randomDirections << m_insideOut;
	*stream << m_fadeFirstToBlack << m_fadeFirstToWhite;
}

//------------------------------------------------------------------------------
//
void 
	gosFX::Tube__Specification::BuildDefaults()
{

	Check_Object(this);
	BaseClass::BuildDefaults();

	m_profilesPerSecond.SetCurve(4.0f);
	m_pLifeSpan.SetCurve(1.0f);

	m_emitterSizeX.SetCurve(0.0f);
	m_emitterSizeY.SetCurve(0.0f);
	m_emitterSizeZ.SetCurve(0.0f);

	m_minimumDeviation.SetCurve(0.0f);
	m_maximumDeviation.SetCurve(0.0f);

	m_pDisplacement.m_ageCurve.SetCurve(0.0f);
	m_pDisplacement.m_seeded = false;
	m_pDisplacement.m_seedCurve.SetCurve(1.0f);

	m_pScale.m_ageCurve.SetCurve(1.0f);
	m_pScale.m_seeded = false;
	m_pScale.m_seedCurve.SetCurve(1.0f);

	m_pRed.m_ageCurve.SetCurve(1.0f);
	m_pRed.m_seeded = false;
	m_pRed.m_seedCurve.SetCurve(1.0f);

	m_pGreen.m_ageCurve.SetCurve(1.0f);
	m_pGreen.m_seeded = false;
	m_pGreen.m_seedCurve.SetCurve(1.0f);

	m_pBlue.m_ageCurve.SetCurve(1.0f);
	m_pBlue.m_seeded = false;
	m_pBlue.m_seedCurve.SetCurve(1.0f);

	m_pAlpha.m_ageCurve.SetCurve(1.0f);
	m_pAlpha.m_seeded = false;
	m_pAlpha.m_seedCurve.SetCurve(1.0f);

	m_pUOffset.m_ageCurve.SetCurve(0.0f);
	m_pUOffset.m_seeded = false;
	m_pUOffset.m_seedCurve.SetCurve(1.0f);

	m_pVOffset.m_ageCurve.SetCurve(0.0f);
	m_pVOffset.m_seeded = false;
	m_pVOffset.m_seedCurve.SetCurve(1.0f);

	m_pUSize.m_ageCurve.SetCurve(1.0f);
	m_pUSize.m_seeded = false;
	m_pUSize.m_seedCurve.SetCurve(1.0f);

	m_pVSize.m_ageCurve.SetCurve(1.0f);
	m_pVSize.m_seeded = false;
	m_pVSize.m_seedCurve.SetCurve(1.0f);

	m_maxProfileCount = 2;
	m_profileType = e_Triangle;
	m_insideOut = false;
	m_randomDirections = false;
	m_fadeFirstToBlack = false;
	m_fadeFirstToWhite = false;

	CalculateUBias(false);
	BuildTemplate();
}


//------------------------------------------------------------------------------
//
bool 
	gosFX::Tube__Specification::IsDataValid(bool fix_data)
{

	Check_Object(this);
	if(m_maxProfileCount<2) 
		if(fix_data)
		{
			m_maxProfileCount=2;
				PAUSE(("Warning: Value \"maxProfileCount\" in Effect \"%s\" Is Out of Range and has been Reset",(char *)m_name));

		}
			else
			return false;
	
	//
	//-----------------------------------------
	// Make sure we don't overwrite MLR buffers
	//-----------------------------------------
	//
	int profile_limit = MidLevelRenderer::Limits::Max_Number_Vertices_Per_Effect/m_vertices.GetLength();
	if (m_maxProfileCount > profile_limit)
	{
		if (fix_data)
		{
			m_maxProfileCount = profile_limit;
			PAUSE(("Warning: Max Profile Count in Effect \"%s\" is too big and has been reset",(char *)m_name));
		}
		else
			return false;
	}

	Stuff::Scalar min,max;
	m_pScale.ExpensiveComputeRange(&min,&max);
	if(min<10.0f*Stuff::SMALL)
	{
		if(fix_data)
		{
			PAUSE(("Warning: Curve \"pScale\" in Effect \"%s\" Is Out of Range and has been Reset",(char *)m_name));
			m_pScale.m_ageCurve.SetCurve(1.0f);
			m_pScale.m_seeded = false;
			m_pScale.m_seedCurve.SetCurve(1.0f);
		}
		else
			return false;
	}

	Stuff::Scalar max_offset, min_offset;
	Stuff::Scalar max_scale, min_scale;
	m_pUSize.ExpensiveComputeRange(&min_scale, &max_scale);
	Stuff::Scalar lower = min_scale;
	if (lower > 0.0f)
		lower = 0.0f;
	Stuff::Scalar upper = max_scale;

	//
	//------------------------------------
	// Calculate the worst case UV offsets
	//------------------------------------
	//
	m_pVOffset.ExpensiveComputeRange(&min_offset, &max_offset);
	lower += min_offset;
	upper += max_offset;

	if (upper > 99.0f || lower < -99.0f)
	{
	if(fix_data)
		{
		m_pVOffset.m_ageCurve.SetCurve(0.0f);
		m_pVOffset.m_seeded = false;
		m_pVOffset.m_seedCurve.SetCurve(1.0f);
		PAUSE(("Warning: Curve \"VOffset\" in Effect \"%s\" Is Out of Range and has been Reset",(char *)m_name));

		}
		else
			return false;
	}
	
	m_pVSize.ExpensiveComputeRange(&min_scale, &max_scale);
	lower = min_scale;
	if (lower > 0.0f)
		lower = 0.0f;
	upper = max_scale;

	//
	//------------------------------------
	// Calculate the worst case UV offsets
	//------------------------------------
	//
	max_offset, min_offset;
	m_pUOffset.ExpensiveComputeRange(&min_offset, &max_offset);
	lower += min_offset;
	upper += max_offset;

	if (upper > 99.0f || lower < -99.0f)
	{
	if(fix_data)
		{
		m_pUOffset.m_ageCurve.SetCurve(0.0f);
		m_pUOffset.m_seeded = false;
		m_pUOffset.m_seedCurve.SetCurve(1.0f);
		PAUSE(("Warning: Curve \"UOffset\" in Effect \"%s\" Is Out of Range and has been Reset",(char *)m_name));
		
		}
		else
			return false;
	}


	return BaseClass::IsDataValid(fix_data);
}

//------------------------------------------------------------------------------
//
void
	gosFX::Tube__Specification::Copy(Tube__Specification *spec)
{
	Check_Object(this);
	Check_Object(spec);

	BaseClass::Copy(spec);

	//
	//----------------
	// Copy the curves
	//----------------
	//
	gos_PushCurrentHeap(Heap);
	m_profilesPerSecond = spec->m_profilesPerSecond;
	m_pLifeSpan = spec->m_pLifeSpan;
	m_emitterSizeX = spec->m_emitterSizeX;
	m_emitterSizeY = spec->m_emitterSizeY;
	m_emitterSizeZ = spec->m_emitterSizeZ;
	m_minimumDeviation = spec->m_minimumDeviation;
	m_maximumDeviation = spec->m_maximumDeviation;
	m_pDisplacement = spec->m_pDisplacement;
	m_pScale = spec->m_pScale;
	m_pRed = spec->m_pRed;
	m_pGreen = spec->m_pGreen;
	m_pBlue = spec->m_pBlue;
	m_pAlpha = spec->m_pAlpha;
	m_pUOffset = spec->m_pUOffset;
	m_pVOffset = spec->m_pVOffset;
	m_pUSize = spec->m_pUSize;
	m_pVSize = spec->m_pVSize;
	m_maxProfileCount = spec->m_maxProfileCount;
	m_profileType = spec->m_profileType;
	m_vertices = spec->m_vertices;
	m_uvs = spec->m_uvs;
	m_insideOut = spec->m_insideOut;
	m_randomDirections = spec->m_randomDirections;
	m_UBias = spec->m_UBias;
	m_fadeFirstToBlack = spec->m_fadeFirstToBlack;
	m_fadeFirstToWhite = spec->m_fadeFirstToWhite;
	gos_PopCurrentHeap();
}

//------------------------------------------------------------------------------
//
void
	gosFX::Tube__Specification::BuildTemplate()
{
	Check_Object(this);

	switch (m_profileType)
	{
	case e_Ribbon:
	case e_AlignedRibbon:
		m_vertices.SetLength(2);
		m_vertices[0] = Stuff::Vector3D(1.0f, 0.0f, 0.0f);
		m_vertices[1] = Stuff::Vector3D(-1.0f, 0.0f, 0.0f);
		m_uvs.SetLength(2);
		m_uvs[0] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 0.0f);
		m_uvs[1] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 1.0f);
		break;

	case e_VerticalRibbon:
		m_vertices.SetLength(2);
		m_vertices[0] = Stuff::Vector3D(0.0f, 0.0f, -1.0f);
		m_vertices[1] = Stuff::Vector3D(0.0f, 0.0f, 1.0f);
		m_uvs.SetLength(2);
		m_uvs[0] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 0.0f);
		m_uvs[1] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 1.0f);
		break;

	case e_Triangle:
		m_vertices.SetLength(4);
		m_vertices[0] = Stuff::Vector3D(0.0f, 0.0f, 1.0f);
		m_vertices[1] = Stuff::Vector3D(-0.8660254f, 0.0f, -0.5f);
		m_vertices[2] = Stuff::Vector3D(0.8660254f, 0.0f, -0.5f);
		m_vertices[3] = Stuff::Vector3D(0.0f, 0.0f, 1.0f);
		m_uvs.SetLength(4);
		m_uvs[0] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 0.0f);
		m_uvs[1] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 1.0f/3.0f);
		m_uvs[2] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 2.0f/3.0f);
		m_uvs[3] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 1.0f);
		break;

	case e_Square:
		m_vertices.SetLength(5);
		m_vertices[0] = Stuff::Vector3D(0.0f, 0.0f, 1.0f);
		m_vertices[1] = Stuff::Vector3D(-1.0f, 0.0f, 0.0f);
		m_vertices[2] = Stuff::Vector3D(0.0f, 0.0f, -1.0f);
		m_vertices[3] = Stuff::Vector3D(1.0f, 0.0f, 0.0f);
		m_vertices[4] = Stuff::Vector3D(0.0f, 0.0f, 1.0f);
		m_uvs.SetLength(5);
		m_uvs[0] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 0.0f);
		m_uvs[1] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 0.25f);
		m_uvs[2] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 0.5f);
		m_uvs[3] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 0.75f);
		m_uvs[4] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 1.0f);
		break;

	case e_Cross:
		m_vertices.SetLength(5);
		m_vertices[0] = Stuff::Vector3D(0.0f, 0.0f, 0.0f);
		m_vertices[1] = Stuff::Vector3D(0.0f, 0.0f, 1.0f);
		m_vertices[2] = Stuff::Vector3D(-1.0f, 0.0f, 0.0f);
		m_vertices[3] = Stuff::Vector3D(0.0f, 0.0f, -1.0f);
		m_vertices[4] = Stuff::Vector3D(1.0f, 0.0f, 0.0f);
		m_uvs.SetLength(5);
		m_uvs[0] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 0.0f);
		m_uvs[1] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 1.0f);
		m_uvs[2] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 1.0f);
		m_uvs[3] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 1.0f);
		m_uvs[4] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 1.0f);
		break;

	case e_Pentagon:
		m_vertices.SetLength(6);
		m_vertices[0] = Stuff::Vector3D(0.0f, 0.0f, 1.0f);
		m_vertices[1] = Stuff::Vector3D(-0.9510565f, 0.0f, 0.309017f);
		m_vertices[2] = Stuff::Vector3D(-0.58778525f, 0.0f, -0.8090167f);
		m_vertices[3] = Stuff::Vector3D(0.58778525f, 0.0f, -0.8090167f);
		m_vertices[4] = Stuff::Vector3D(0.9510565f, 0.0f, 0.309017f);
		m_vertices[5] = Stuff::Vector3D(0.0f, 0.0f, 1.0f);
		m_uvs.SetLength(6);
		m_uvs[0] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 0.0f);
		m_uvs[1] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 0.2f);
		m_uvs[2] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 0.4f);
		m_uvs[3] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 0.6f);
		m_uvs[4] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 0.8f);
		m_uvs[5] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 1.0f);
		break;

	case e_Hexagon:
		m_vertices.SetLength(7);
		m_vertices[0] = Stuff::Vector3D(0.0f, 0.0f, 1.0f);
		m_vertices[1] = Stuff::Vector3D(-0.8660254f, 0.0f, 0.5f);
		m_vertices[2] = Stuff::Vector3D(-0.8660254f, 0.0f, -0.5f);
		m_vertices[3] = Stuff::Vector3D(0.0f, 0.0f, -1.0f);
		m_vertices[4] = Stuff::Vector3D(0.8660254f, 0.0f, -0.5f);
		m_vertices[5] = Stuff::Vector3D(0.8660254f, 0.0f, 0.5f);
		m_vertices[6] = Stuff::Vector3D(0.0f, 0.0f, 1.0f);
		m_uvs.SetLength(7);
		m_uvs[0] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 0.0f);
		m_uvs[1] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 1.0f/6.0f);
		m_uvs[2] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 2.0f/6.0f);
		m_uvs[3] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 0.5f);
		m_uvs[4] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 4.0f/6.0f);
		m_uvs[5] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 5.0f/6.0f);
		m_uvs[6] = Stuff::Vector2DOf<Stuff::Scalar>(0.0f, 1.0f);
		break;
	}
}

//------------------------------------------------------------------------------
//
bool
	gosFX::Tube__Specification::CalculateUBias(bool adjust)
{
	Check_Object(this);

	//
	//----------------------------------
	// Calculate the worst case UV scale
	//----------------------------------
	//
	int max_index = m_maxProfileCount-1;
	Stuff::Scalar max_scale, min_scale;
Retry:
	m_pUSize.ExpensiveComputeRange(&min_scale, &max_scale);
	Stuff::Scalar lower = min_scale * max_index;
	if (lower > 0.0f)
		lower = 0.0f;
	Stuff::Scalar upper = max_scale * max_index;

	//
	//------------------------------------
	// Calculate the worst case UV offsets
	//------------------------------------
	//
	Stuff::Scalar max_offset, min_offset;
	m_pUOffset.ExpensiveComputeRange(&min_offset, &max_offset);
	lower += min_offset;
	upper += max_offset;
	if (upper - lower >= 198.0f)
	{
		if (!adjust)
			return false;
		lower = max_offset - min_offset;
		Stuff::Scalar scale = static_cast<Stuff::Scalar>(floor((198.0f-lower)/max_index));
		m_pUSize.m_ageCurve.SetCurve(scale);
		m_pUSize.m_seeded = false;
		goto Retry;
	}
	lower += 99.0f;
	if (lower < 0.0f)
	{
		m_UBias = static_cast<Stuff::Scalar>(floor(0.5f-lower));
		return true;
	}
	upper -= 99.0f;
	if (upper > 0.0f)
	{
		m_UBias = static_cast<Stuff::Scalar>(floor(0.5f-upper));
		return true;
	}
	m_UBias = 0.0f;
	return true;
}

//############################################################################
//############################  gosFX::Tube  ###############################
//############################################################################

gosFX::Tube::ClassData*
	gosFX::Tube::DefaultData = NULL;

//------------------------------------------------------------------------------
//
void
	gosFX::Tube::InitializeClass()
{
	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			TubeClassID,
			"gosFX::Tube",
			BaseClass::DefaultData,
			(Effect::Factory)&Make,
			(Specification::Factory)&Specification::Make
		);
	Check_Object(DefaultData);
}

//------------------------------------------------------------------------------
//
void
	gosFX::Tube::TerminateClass()
{
	Check_Object(DefaultData);
	delete DefaultData;
	DefaultData = NULL;
}

//------------------------------------------------------------------------------
//
gosFX::Tube::Tube(
	Specification *spec,
	unsigned flags
):
	Effect(DefaultData, spec, flags)
{
	Check_Pointer(this);
	Check_Object(spec);

	//
	//----------------------------------
	// Figure out how much space we need
	//----------------------------------
	//
	m_profiles.SetLength(spec->m_maxProfileCount);
	unsigned vertex_count = spec->m_vertices.GetLength();
	Verify(vertex_count < 8);
	unsigned index_count = (vertex_count - 1)*6;
	unsigned size =
		sizeof(Stuff::Point3D)
		 + sizeof(Stuff::RGBAColor)
		 + sizeof(Stuff::Vector2DOf<Stuff::Scalar>);
	size *= vertex_count*spec->m_maxProfileCount;
	size += sizeof(unsigned short)*(spec->m_maxProfileCount-1)*index_count;

	//
	//-----------------------
	// Allocate the tube mesh
	//-----------------------
	//
	gos_PushCurrentHeap(MidLevelRenderer::EffectHeap);
	m_mesh =
		new MidLevelRenderer::MLRIndexedTriangleCloud(
			spec->m_maxProfileCount*(vertex_count-1)*2
		);
	Check_Object(m_mesh);
	gos_PopCurrentHeap();

	//
	//------------------------------------------------
	// Set up the data pointers into the channel block
	//------------------------------------------------
	//
	m_triangleCount = 0;
	m_vertexCount = 0;
	m_data.SetLength(size);
	m_P_vertices = Cast_Pointer(Stuff::Point3D*, &m_data[0]);
	size = spec->m_maxProfileCount*vertex_count*sizeof(Stuff::Point3D);
	m_P_colors = Cast_Pointer(Stuff::RGBAColor*, &m_data[size]);
	size += spec->m_maxProfileCount*vertex_count*sizeof(Stuff::RGBAColor);
	m_P_uvs = Cast_Pointer(Stuff::Vector2DOf<Stuff::Scalar>*, &m_data[size]);
	size += spec->m_maxProfileCount*vertex_count*sizeof(Stuff::Vector2DOf<Stuff::Scalar>);
	unsigned short *mesh_indices = Cast_Pointer(unsigned short*, &m_data[size]);
	m_mesh->SetData(
		&m_triangleCount,
		&m_vertexCount,
		mesh_indices,
		m_P_vertices,
		m_P_colors,
		m_P_uvs
	);
	BuildMesh(mesh_indices);

	//
	//-------------------------------
	// Set up an empty profile cloud
	//-------------------------------
	//
	m_activeProfileCount = 0;
	m_headProfile = -1;
	m_tailProfile = -1;
	m_firstProfile = -1;
	m_birthAccumulator = 0.0f;
}

//------------------------------------------------------------------------------
//
void
	gosFX::Tube::BuildMesh(unsigned short *indices)
{
	Check_Object(this);
	Check_Pointer(indices);

	//
	//--------------------------------------
	// Figure out the parameters of the mesh
	//--------------------------------------
	//
	Specification *spec = GetSpecification();
	Check_Object(spec);
	unsigned vertex_count = spec->m_vertices.GetLength();
	Verify(vertex_count < 8);

	//
	//-------------------------------------------------
	// Crosses are built different from everything else
	//-------------------------------------------------
	//
	if (spec->m_profileType != Specification::e_Cross)
	{
		for (unsigned short profile=0; profile<spec->m_maxProfileCount-1; ++profile)
		{
			unsigned short base = static_cast<short>(profile * vertex_count);
			for (unsigned short panel=0; panel<vertex_count-1; ++panel)
			{
				if (spec->m_insideOut)
				{
					*indices++ = static_cast<short>(base+panel+1);
					*indices++ = static_cast<short>(base+panel);
					*indices++ = static_cast<short>(base+panel+vertex_count+1);
					*indices++ = static_cast<short>(base+panel+vertex_count);
					*indices++ = static_cast<short>(base+panel+vertex_count+1);
					*indices++ = static_cast<short>(base+panel);
				}
				else
				{
					*indices++ = static_cast<short>(base+panel);
					*indices++ = static_cast<short>(base+panel+1);
					*indices++ = static_cast<short>(base+panel+vertex_count+1);
					*indices++ = static_cast<short>(base+panel+vertex_count+1);
					*indices++ = static_cast<short>(base+panel+vertex_count);
					*indices++ = static_cast<short>(base+panel);
				}
			}
		}
	}
	else
	{
		Verify(vertex_count==5);
		for (unsigned short profile=0; profile<spec->m_maxProfileCount-1; ++profile)
		{
			unsigned short base = static_cast<short>(profile * vertex_count);
			for (unsigned short panel=0; panel<4; ++panel)
			{
				if (spec->m_insideOut)
				{
					*indices++ = static_cast<short>(base+panel+1);
					*indices++ = base;
					*indices++ = static_cast<short>(base+panel+6);
					*indices++ = static_cast<short>(base+5);
					*indices++ = static_cast<short>(base+panel+6);
					*indices++ = base;
				}
				else
				{
					*indices++ = base;
					*indices++ = static_cast<short>(base+panel+1);
					*indices++ = static_cast<short>(base+panel+6);
					*indices++ = static_cast<short>(base+panel+6);
					*indices++ = static_cast<short>(base+5);
					*indices++ = base;
				}
			}
		}
	}
}

//------------------------------------------------------------------------------
//
gosFX::Tube::~Tube()
{
	Check_Object(this);
	Check_Object(m_mesh);
	delete m_mesh;
}

//------------------------------------------------------------------------------
//
gosFX::Tube*
	gosFX::Tube::Make(
		Specification *spec,
		unsigned flags
	)
{
	Check_Object(spec);

	gos_PushCurrentHeap(Heap);
	Tube *tube = new gosFX::Tube(spec, flags);
	gos_PopCurrentHeap();

	return tube;
}

//------------------------------------------------------------------------------
//
void
	gosFX::Tube::Start(ExecuteInfo *info)
{
	Check_Object(this);
	Check_Pointer(info);

	//
	//----------------------
	// Let effect initialize
	//----------------------
	//
	BaseClass::Start(info);

	//
	//----------------------------------------------------------------------
	// Update the head of the tube to the current location, then compute the
	// inverse transformation to keep all particles in their place of origin
	//----------------------------------------------------------------------
	//
	Stuff::LinearMatrix4D new_world_to_local;
	Stuff::LinearMatrix4D local_to_world;
	local_to_world.Multiply(m_localToParent, *info->m_parentToWorld);
	new_world_to_local.Invert(local_to_world);

	//
	//------------------------------------------------------------------
	// If it is time for a new child and there is room, move the head of
	// the tube and create the new profile, then clear out the integer
	// part of the accumulator - stacking up would look stupid
	//------------------------------------------------------------------
	//
	Specification *spec = GetSpecification();
	Check_Object(spec);
	m_birthAccumulator = 0.0f;
	if (m_activeProfileCount < spec->m_maxProfileCount)
	{
		if (!m_activeProfileCount)
		{
			m_headProfile = 1;
			m_firstProfile = 0;
			m_tailProfile = spec->m_maxProfileCount-1;
			CreateNewProfile(0, local_to_world);
			m_activeProfileCount = 2;
		}
		else
		{
			m_profiles[m_headProfile].m_profileToWorld = local_to_world;
			if (++m_headProfile == spec->m_maxProfileCount)
				m_headProfile = 0;
		}
		CreateNewProfile(m_headProfile, local_to_world);
	}
	else
		m_profiles[m_headProfile].m_profileToWorld = local_to_world;
}

//------------------------------------------------------------------------------
//
bool gosFX::Tube::Execute(ExecuteInfo *info)
{
	Check_Object(this);
	Check_Object(info);
	GFX_LOGIC("Execute::Tube");

	//
	//----------------------------------------
	// If we aren't supposed to execute, don't
	//----------------------------------------
	//
	if (!IsExecuted())
		return false;

	//
	//----------------------------------------------------------------------
	// Update the head of the tube to the current location, then compute the
	// inverse transformation to keep all particles in their place of origin
	//----------------------------------------------------------------------
	//
	Stuff::LinearMatrix4D new_world_to_local;
	Stuff::LinearMatrix4D local_to_world;
	local_to_world.Multiply(m_localToParent, *info->m_parentToWorld);
	new_world_to_local.Invert(local_to_world);

	//
	//--------------------------
	// Figure out the birth rate
	//--------------------------
	//
	Specification *spec = GetSpecification();
	Check_Object(spec);
	Stuff::Scalar dT =
		static_cast<Stuff::Scalar>(info->m_time - m_lastRan);
	Verify(dT >= 0.0f);
	if (m_age + dT * m_ageRate >= 1.0f)
	{
		m_birthAccumulator = 0.0f;
		if (m_age < 1.0f)
			m_profiles[m_headProfile].m_profileToWorld = local_to_world;
	}
	else
	{
		Stuff::Scalar new_life =
			spec->m_profilesPerSecond.ComputeValue(m_age, m_seed);
		Min_Clamp(new_life, 0.0f);
		m_birthAccumulator += dT * new_life;

		//
		//------------------------------------------------------------------
		// If it is time for a new child and there is room, move the head of
		// the tube and create the new profile, then clear out the integer
		// part of the accumulator - stacking up would look stupid
		//------------------------------------------------------------------
		//
		if (m_birthAccumulator >= 1.0f && m_activeProfileCount < spec->m_maxProfileCount && m_age>0.0f)
		{
			if (!m_activeProfileCount)
			{
				m_headProfile = 1;
				m_firstProfile = 0;
				m_tailProfile = spec->m_maxProfileCount-1;
				CreateNewProfile(0, local_to_world);
				m_activeProfileCount = 2;
			}
			else
			{
				m_profiles[m_headProfile].m_profileToWorld = local_to_world;
				if (++m_headProfile == spec->m_maxProfileCount)
					m_headProfile = 0;
			}
			CreateNewProfile(m_headProfile, local_to_world);
			m_birthAccumulator -= static_cast<Stuff::Scalar>(floor(m_birthAccumulator));
		}
		else
			m_profiles[m_headProfile].m_profileToWorld = local_to_world;
	}

	//
	//--------------------------------------------------------------------
	// If we don't have any profiles, just execute the children and return
	//--------------------------------------------------------------------
	//
	if (!m_activeProfileCount)
		return BaseClass::Execute(info);

	//
	//----------------------------------
	// Deal with all the active profiles
	//----------------------------------
	//
	Stuff::ExtentBox box(Stuff::Point3D::Identity, Stuff::Point3D::Identity);
	int i = m_headProfile;
	int profile_count = 0;
	Verify(i >= 0);
	do
	{
		//
		//----------------------------------------------------------------------
		// Age and animate the profile.  If the profile should die, it becomes
		// the end of the tail
		//----------------------------------------------------------------------
		//
		Profile *profile = GetProfile(i);
		Check_Object(profile);
		Verify (profile->m_age < 1.0f);
		profile->m_age += dT*profile->m_ageRate;
		Stuff::Sphere bounds;
		if (!AnimateProfile(i, profile_count, new_world_to_local, info->m_time, &bounds))
		{
			m_tailProfile = i;
			break;
		}

		//
		//---------------------
		// Deal with the bounds
		//---------------------
		//
		if (i == m_headProfile)
		{
			box.maxX = bounds.center.x + bounds.radius;
			box.minX = bounds.center.x - bounds.radius;
			box.maxY = bounds.center.y + bounds.radius;
			box.minY = bounds.center.y - bounds.radius;
			box.maxZ = bounds.center.z + bounds.radius;
			box.minZ = bounds.center.z - bounds.radius;
		}
		else
		{
			Stuff::ExtentBox local_box;
			local_box.maxX = bounds.center.x + bounds.radius;
			local_box.minX = bounds.center.x - bounds.radius;
			local_box.maxY = bounds.center.y + bounds.radius;
			local_box.minY = bounds.center.y - bounds.radius;
			local_box.maxZ = bounds.center.z + bounds.radius;
			local_box.minZ = bounds.center.z - bounds.radius;
			box.Union(box, local_box);
		}

		//
		//---------------------------------------------------------------------------
		// Move to the previous profile and wrap to the end of the list if necessary
		//---------------------------------------------------------------------------
		//
		++profile_count;
		if (--i < 0)
			i = spec->m_maxProfileCount-1;
	} while (i != m_tailProfile);
	m_activeProfileCount = profile_count;

	//
	//-----------------------------------------------
	// Put the age back and run the base effect stuff
	//-----------------------------------------------
	//
	if (!BaseClass::Execute(info))
		return false;

	//
	//--------------------------------------------
	// Now, build a info->m_bounds around this box
	//--------------------------------------------
	//
	Verify(box.maxX >= box.minX);
	Verify(box.maxY >= box.minY);
	Verify(box.maxZ >= box.minZ);
	Stuff::OBB local_bounds = Stuff::OBB::Identity;
	local_bounds.axisExtents.x = 0.5f * (box.maxX - box.minX);
	local_bounds.axisExtents.y = 0.5f * (box.maxY - box.minY);
	local_bounds.axisExtents.z = 0.5f * (box.maxZ - box.minZ);
	local_bounds.localToParent(3,0) = box.minX + local_bounds.axisExtents.x;
	local_bounds.localToParent(3,1) = box.minY + local_bounds.axisExtents.y;
	local_bounds.localToParent(3,2) = box.minZ + local_bounds.axisExtents.z;
	local_bounds.sphereRadius = local_bounds.axisExtents.GetLength() + Stuff::SMALL;
	Stuff::OBB parent_bounds;
	parent_bounds.Multiply(local_bounds, m_localToParent);
	info->m_bounds->Union(*info->m_bounds, parent_bounds);
	return true;
}

//------------------------------------------------------------------------------
//
void gosFX::Tube::Kill()
{
	Check_Object(this);

	//
	//-------------------------------------------------------------
	// Destroy all the profiles and set up an empty profile cloud
	//-------------------------------------------------------------
	//
	Specification *spec = GetSpecification();
	Check_Object(spec);
	if (m_activeProfileCount>0)
	{
		do
		{
			DestroyProfile(m_headProfile--);
			if (m_headProfile<0)
				m_headProfile = spec->m_maxProfileCount-1;
		} while (m_headProfile != m_tailProfile);
	}
	m_activeProfileCount = 0;
	m_headProfile = -1;
	m_tailProfile = -1;
	m_firstProfile = -1;
	m_birthAccumulator = 0.0f;

	//
	//----------------------------------------
	// Now let the base effect handle stopping
	//----------------------------------------
	//
	BaseClass::Kill();
}

//------------------------------------------------------------------------------
//
bool
	gosFX::Tube::HasFinished()
{
	Check_Object(this);
	return BaseClass::HasFinished() && (m_activeProfileCount == 0);
}

//------------------------------------------------------------------------------
//
void
	gosFX::Tube::ComputeDirection(unsigned index)
{
	Specification *spec = GetSpecification();
	Check_Object(spec);
	Profile *profile = GetProfile(index);
	Check_Object(profile);

	Stuff::Scalar seed = profile->m_seed;
	Stuff::Scalar pitch_min =
		spec->m_minimumDeviation.ComputeValue(m_age, seed);
	Stuff::Scalar pitch_range =
		spec->m_maximumDeviation.ComputeValue(m_age, seed) - pitch_min;
	if (pitch_range < 0.0f)
		pitch_range = 0.0f;
	Stuff::Radian angle = pitch_min + Stuff::Random::GetFraction() * pitch_range;
	Stuff::SinCosPair xy(angle);
	profile->m_direction.x = (Stuff::Random::GetFraction() >= 0.5f) ? xy.sine : -xy.sine;
	profile->m_direction.y = 0.0f;
	profile->m_direction.z = xy.cosine;
	Check_Object(&profile->m_direction);
}

//------------------------------------------------------------------------------
//
void
	gosFX::Tube::CreateNewProfile(
		unsigned index,
		const Stuff::LinearMatrix4D &origin
	)
{
	Check_Object(this);
	Check_Object(&origin);

	//
	//----------------------------------------------------
	// Figure out the age and age rate of the new profile
	//----------------------------------------------------
	//
	Specification *spec = GetSpecification();
	Check_Object(spec);
	Profile *profile = GetProfile(index);
	Check_Object(profile);
	profile->m_age = 0.0f;
	Stuff::Scalar min_seed =
		spec->m_minimumChildSeed.ComputeValue(m_age, m_seed);
	Stuff::Scalar seed_range =
		spec->m_maximumChildSeed.ComputeValue(m_age, m_seed) - min_seed;
	Stuff::Scalar seed =
		Stuff::Random::GetFraction()*seed_range + min_seed;
	Clamp(seed, 0.0f, 1.0f);
	profile->m_seed = seed;
	Stuff::Scalar lifetime =
		spec->m_pLifeSpan.ComputeValue(m_age, seed);
	Min_Clamp(lifetime, 0.0333333f);
	profile->m_ageRate = 1.0f / lifetime;

	//
	//------------------------------------------------------------------
	// Establish the base position and figure out the direction of drift
	//------------------------------------------------------------------
	//
	profile->m_profileToWorld = origin;
	ComputeDirection(index);
	Check_Object(&profile->m_direction);
}

//------------------------------------------------------------------------------
//
bool
	gosFX::Tube::AnimateProfile(
		unsigned index,
		unsigned profile_index,
		const Stuff::LinearMatrix4D &world_to_new_local,
		Stuff::Time till,
		Stuff::Sphere *bounds
	)
{
	Check_Object(this);

	//
	//----------------------------------------------------
	// If the profile gets too old, don't do anything else
	//----------------------------------------------------
	//
	Profile *profile = GetProfile(index);
	Check_Object(profile);
	Stuff::Scalar age = profile->m_age;
	if (age >= 1.0f)
		return false;

	//
	//--------------------------------------------------------------------
	// Figure out the scale and displacement of the profile in world space
	//--------------------------------------------------------------------
	//
	Set_Statistic(Profile_Count, Profile_Count+1);
	Stuff::Scalar seed = profile->m_seed;
	Specification *spec = GetSpecification();
	Check_Object(spec);
	Stuff::Scalar scale = spec->m_pScale.ComputeValue(age, seed);
	Verify(scale > 0.0f);
	Stuff::Scalar disp = spec->m_pDisplacement.ComputeValue(age, seed);
	Stuff::Point3D offset;
	if (spec->m_randomDirections)
		ComputeDirection(profile_index);
	offset.Multiply(profile->m_direction, disp);

	//
	//-------------------------------------------------
	// Now build the template to new local space matrix
	//-------------------------------------------------
	//
	Stuff::AffineMatrix4D template_to_profile(true);
	template_to_profile(0,0) = scale;
	template_to_profile(1,1) = scale;
	template_to_profile(2,2) = scale;
	template_to_profile.BuildTranslation(offset);
	Stuff::AffineMatrix4D template_to_world;
	template_to_world.Multiply(template_to_profile, profile->m_profileToWorld);
	Stuff::AffineMatrix4D template_to_new_local;
	template_to_new_local.Multiply(template_to_world, world_to_new_local);
	bounds->center = template_to_new_local;
	bounds->radius = scale;

	//
	//------------------------------------------------------------------------
	// Now we just multiply the template through to the vertices unless we are
	// doing the aligned ribbon
	//------------------------------------------------------------------------
	//
	unsigned i;
	unsigned vertex_count = spec->m_vertices.GetLength();
	Verify(vertex_count < 8);
	unsigned vertex_index = profile_index * vertex_count;
	if (spec->m_profileType != Specification::e_AlignedRibbon)
	{
		Check_Pointer(m_P_vertices);
		for (i=0; i<vertex_count; ++i)
		{
			m_P_vertices[vertex_index+i].Multiply(
				spec->m_vertices[i],
				template_to_new_local
			);
		}
	}

	//
	//------------------------------
	// Figure out the UV adjustments
	//------------------------------
	//
	Check_Pointer(m_P_uvs);
	Stuff::Scalar u = spec->m_pUOffset.ComputeValue(age, seed);
	Stuff::Scalar v = spec->m_pVOffset.ComputeValue(age, seed);
	Stuff::Scalar u2 = spec->m_pUSize.ComputeValue(age, seed);
	Stuff::Scalar v2 = spec->m_pVSize.ComputeValue(age, seed);
	u += u2*profile_index + spec->m_UBias;
	for (i=0; i<vertex_count; ++i)
	{
		m_P_uvs[vertex_index+i].x = u;
		m_P_uvs[vertex_index+i].y = spec->m_uvs[i].y*v2 + v;
	}

	//
	//-------------------------------------------------------------------------
	// Lastly, animate the colors.  If this is the head profile, we need to see
	// if the color should be overridden to either white or black
	//-------------------------------------------------------------------------
	//
	Check_Pointer(m_P_colors);
	Stuff::RGBAColor color;
	if (index == m_firstProfile)
	{
		if (spec->m_fadeFirstToWhite)
		{
			color.red = 1.0f;
			color.green = 1.0f;
			color.blue = 1.0f;
			color.alpha = 0.0f;
		}
		else if (spec->m_fadeFirstToBlack)
		{
			color.red = 0.0f;
			color.green = 0.0f;
			color.blue = 0.0f;
			color.alpha = 0.0f;
		}
		else goto Animate_Color;
	}
	else
	{
Animate_Color:
		color.red = spec->m_pRed.ComputeValue(age, seed);
		color.green = spec->m_pGreen.ComputeValue(age, seed);
		color.blue = spec->m_pBlue.ComputeValue(age, seed);
		color.alpha = spec->m_pAlpha.ComputeValue(age, seed);
	}

	for (i=0; i<vertex_count; ++i)
		m_P_colors[vertex_index+i] = color;
	return true;
}

//------------------------------------------------------------------------------
//
void gosFX::Tube::DestroyProfile(unsigned index)
{
	Profile *profile = GetProfile(index);
	Check_Object(profile);
	profile->m_age = 1.0f;
	if (index==m_firstProfile)
		m_firstProfile = -1;
}

//------------------------------------------------------------------------------
//
void gosFX::Tube::Draw(DrawInfo *info)
{
	Check_Object(this);
	Check_Object(info);
	GFX_RENDER("Tube");

	//
	//---------------------------------------------------------
	// If we have active particles, set up the draw information
	//---------------------------------------------------------
	//
	if (m_activeProfileCount>1)
	{
		MidLevelRenderer::DrawEffectInformation dInfo;
		dInfo.effect = m_mesh;
		Specification *spec = GetSpecification();
		Check_Object(spec);
		dInfo.state.Combine(info->m_state, spec->m_state);
		dInfo.clippingFlags = info->m_clippingFlags;
		Stuff::LinearMatrix4D local_to_world;
		local_to_world.Multiply(m_localToParent, *info->m_parentToWorld);
		dInfo.effectToWorld = &local_to_world;
		unsigned vertex_count = spec->m_vertices.GetLength();
		m_vertexCount = m_activeProfileCount * vertex_count;
		m_triangleCount = 2 * (m_activeProfileCount-1) * (vertex_count-1);

		//
		//-------------------------------------------------------------------
		// If we are doing the aligned ribbon, we will have to orient each of
		// the profiles and then compute its vertex positions accordingly
		//-------------------------------------------------------------------
		//
		if (spec->m_profileType == Specification::e_AlignedRibbon)
		{
			int i = m_headProfile;
			int vertex = 0;
			unsigned vertex_count = spec->m_vertices.GetLength();
			Verify(vertex_count < 8);
			Stuff::Point3D
				camera_in_world(info->m_clipper->GetCameraToWorldMatrix());
			Stuff::LinearMatrix4D world_to_local;
			world_to_local.Invert(local_to_world);

			//
			//----------------------------------------------------------------
			// go thru all the profiles and figure out where they are in local
			// space
			//----------------------------------------------------------------
			//
			Verify(i >= 0);
			do
			{
				Profile *profile = GetProfile(i);
				Check_Object(profile);
				Stuff::Point3D camera_in_profile;
				camera_in_profile.MultiplyByInverse(camera_in_world, profile->m_profileToWorld);

				//
				//---------------------------------------------------------
				// Figure out the scale and displacement of the template in
				// profile space
				//---------------------------------------------------------
				//
				Stuff::Scalar age = profile->m_age;
				Stuff::Scalar seed = profile->m_seed;
				Stuff::Scalar scale = spec->m_pScale.ComputeValue(age, seed);
				Stuff::Scalar disp = spec->m_pDisplacement.ComputeValue(age, seed);
				Stuff::Point3D offset_in_profile;
				offset_in_profile.Multiply(profile->m_direction, disp);

				//
				//----------------------------------------------------------
				// Figure out the direction that we want the profile to face
				// and build a rotation vector that does it
				//----------------------------------------------------------
				//
				Stuff::Vector3D direction;
				direction.Subtract(camera_in_profile, offset_in_profile);
				Stuff::LinearMatrix4D template_rotation(true);
				template_rotation.AlignLocalAxisToWorldVector(
					direction,
					Stuff::Z_Axis,
					Stuff::Y_Axis,
					-1
				);

				//
				//-------------------------------------------------
				// Now build the template to new local space matrix
				//-------------------------------------------------
				//
				Stuff::AffineMatrix4D template_to_profile;
				template_to_profile(0,0) = scale*template_rotation(0,0);
				template_to_profile(0,1) = scale*template_rotation(0,1);
				template_to_profile(0,2) = scale*template_rotation(0,2);
				template_to_profile(1,0) = scale*template_rotation(1,0);
				template_to_profile(1,1) = scale*template_rotation(1,1);
				template_to_profile(1,2) = scale*template_rotation(1,2);
				template_to_profile(2,0) = scale*template_rotation(2,0);
				template_to_profile(2,1) = scale*template_rotation(2,1);
				template_to_profile(2,2) = scale*template_rotation(2,2);
				template_to_profile.BuildTranslation(offset_in_profile);
				Stuff::AffineMatrix4D template_to_world;
				template_to_world.Multiply(template_to_profile, profile->m_profileToWorld);
				Stuff::AffineMatrix4D template_to_local;
				template_to_local.Multiply(template_to_world, world_to_local);

				//
				//------------------------------------
				// Multiply the points thru the matrix
				//------------------------------------
				//
				for (int v=0; v<vertex_count; ++v)
				{
					m_P_vertices[vertex++].Multiply(
						spec->m_vertices[v],
						template_to_local
					);
				}

				//
				//---------------------------------------------------------------------------
				// Move to the previous profile and wrap to the end of the list if necessary
				//---------------------------------------------------------------------------
				//
				if (--i < 0)
					i = spec->m_maxProfileCount-1;
			} while (i != m_tailProfile);
		}

		//
		//--------------------
		// Now draw the effect
		//--------------------
		//
	 	info->m_clipper->DrawEffect(&dInfo);
	}

	BaseClass::Draw(info);
}

//------------------------------------------------------------------------------
//
void
	gosFX::Tube::TestInstance() const
{
	Verify(IsDerivedFrom(DefaultData));
}
