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

//==========================================================================//
// File:	 gosFX_ShapeCloud.cpp											//
// Project:  gosFX					                                        //
// Contents: Base gosFX::ShapeCloud 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::ShapeCloud__Specification  #############################
//############################################################################

//------------------------------------------------------------------------------
//
gosFX::ShapeCloud__Specification::ShapeCloud__Specification(
	Stuff::MemoryStream *stream,
	int gfx_version
):
	SpinningCloud__Specification(gosFX::ShapeCloudClassID, stream, gfx_version)
{
	Check_Pointer(this);
	Verify(m_class == ShapeCloudClassID);
	m_particleClassSize = sizeof(gosFX::ShapeCloud::Particle);
	m_totalParticleSize = sizeof(gosFX::ShapeCloud::Particle);

	//
	//---------------------------------
	// Ditch the shape if its old style
	//---------------------------------
	//
	if (gfx_version < 25)
	{
		MidLevelRenderer::MLRShape *shape =
			MidLevelRenderer::MLRShape::Make(
				stream,
				MidLevelRenderer::ReadMLRVersion(stream)
			);
		Check_Object(shape);
		shape->DetachReference();
		*stream >> m_radius;
		m_shape = NULL;
	}

	//
	//----------------------------------------
	// Otherwise, read the filename and radius
	//----------------------------------------
	//
	else
	{
		*stream >> m_shapeFile >> m_radius;

		if(m_shapeFile)
			m_shape = EffectLibrary::Instance->UseShape(m_shapeFile);
		else
			m_shape = NULL;

	}
}

//------------------------------------------------------------------------------
//
gosFX::ShapeCloud__Specification::ShapeCloud__Specification():
	SpinningCloud__Specification(gosFX::ShapeCloudClassID)
{
	Check_Pointer(this);
	m_totalParticleSize = m_particleClassSize = sizeof(gosFX::ShapeCloud::Particle);
	m_shape = NULL;
	m_radius=0.0f;

}

//------------------------------------------------------------------------------
//
gosFX::ShapeCloud__Specification::~ShapeCloud__Specification()
{
	Check_Object(this);
	if (m_shape)
	{
		Check_Object(m_shape);
		m_shape->DetachReference();
	}
}

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

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

	return spec;
}

//------------------------------------------------------------------------------
//
void
	gosFX::ShapeCloud__Specification::Save(Stuff::MemoryStream *stream)
{
	Check_Object(this);
	Check_Object(stream);
	BaseClass::Save(stream);
	*stream << m_shapeFile << m_radius;
}

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

	BaseClass::Copy(spec);

	gos_PushCurrentHeap(Heap);
	m_radius = spec->m_radius;
	m_shape = spec->m_shape;
	gos_PopCurrentHeap();

	Check_Object(m_shape);
	m_shape->AttachReference();
}

//------------------------------------------------------------------------------
//
void
	gosFX::ShapeCloud__Specification::SetShape(Stuff::MString &shape)
{
	Check_Object(this);
	m_shapeFile=shape;

	//
	//------------------------------------
	// Detach the old shape if it is there
	//------------------------------------
	//
	if (m_shape)
	{
		Check_Object(m_shape);
		m_shape->DetachReference();
	}

	//
	//------------------------------------
	// Attach the new shape if it is there
	//------------------------------------
	//
	m_shape = EffectLibrary::Instance->UseShape(shape);

	//
	//-----------------------------------------------------------------
	// Get the radius of the bounding sphere.  This will be the largest
	// distance any point is from the origin
	//-----------------------------------------------------------------
	//
	m_radius = 0.0f;
	int count = m_shape->GetNum();
	for (int i=0; i<count; ++i)
	{
		MidLevelRenderer::MLRPrimitiveBase *primitive = m_shape->Find(i);
		Check_Object(primitive);
		const Stuff::Point3D *points;
		int vertex_count;
		primitive->GetCoordData(&points, &vertex_count);
		for (int v=0; v<vertex_count; ++v)
		{
			Stuff::Scalar len = points[v].GetLengthSquared();
			if (len > m_radius)
				m_radius = len;
		}
	}
	m_radius = Stuff::Sqrt(m_radius);
}

//############################################################################
//##############################  gosFX::ShapeCloud  ################################
//############################################################################

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

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

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

//------------------------------------------------------------------------------
//
gosFX::ShapeCloud::ShapeCloud(
	Specification *spec,
	unsigned flags
):
	SpinningCloud(DefaultData, spec, flags)
{
}

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

	gos_PushCurrentHeap(Heap);
	ShapeCloud *cloud = new gosFX::ShapeCloud(spec, flags);
	gos_PopCurrentHeap();

	return cloud;
}

//------------------------------------------------------------------------------
//
void
	gosFX::ShapeCloud::CreateNewParticle(
		unsigned index,
		Stuff::Point3D *translation
	)
{
	Check_Object(this);

	//
	//-------------------------------------------------------------------
	// Let our parent do creation, then turn on the particle in the cloud
	//-------------------------------------------------------------------
	//
	BaseClass::CreateNewParticle(index, translation);
	Specification *spec = GetSpecification();
	Check_Object(spec);
	Particle *particle = GetParticle(index);
	Check_Object(particle);
	particle->m_radius = spec->m_radius;
}

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

	//
	//--------------------------------------
	// Load the shape if it isn't loaded yet
	//--------------------------------------
	//
	Specification *spec = GetSpecification();
	Check_Object(spec);
	if (spec->m_shapeFile && !spec->m_shape)
	{
		spec->m_shape = EffectLibrary::Instance->UseShape(spec->m_shapeFile);
		Check_Object(spec->m_shape);
	}

	BaseClass::Start(info);
}

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

	//
	//----------------------------
	// Set up the common draw info
	//----------------------------
	//
	if (m_activeParticleCount)
	{
		MidLevelRenderer::DrawScalableShapeInformation dinfo;
		MidLevelRenderer::MLRShape *shape = GetSpecification()->m_shape;
		if (!shape)
			return;
		dinfo.clippingFlags.SetClippingState(0x3f);
		dinfo.worldToShape = NULL;
		Specification *spec = GetSpecification();
		Check_Object(spec);
		dinfo.state.Combine(info->m_state, spec->m_state);
		dinfo.activeLights = NULL;
		dinfo.nrOfActiveLights = 0;
		dinfo.shape = shape;
		Stuff::LinearMatrix4D local_to_world;
		local_to_world.Multiply(m_localToParent, *info->m_parentToWorld);

		//
		//--------------------------------------------------------------
		// Check the orientation mode.  The first case is XY orientation
		//--------------------------------------------------------------
		//
		unsigned i;
		if (spec->m_alignZUsingX)
		{
			if (spec->m_alignZUsingY)
			{
				//
				//-----------------------------------------
				// Get the camera location into local space
				//-----------------------------------------
				//
				Stuff::Point3D
					camera_in_world(info->m_clipper->GetCameraToWorldMatrix());
				Stuff::Point3D camera_in_cloud;
				camera_in_cloud.MultiplyByInverse(
					camera_in_world,
					local_to_world
				);
				for (unsigned i = 0; i < m_activeParticleCount; i++)
				{
					Particle *particle = GetParticle(i);
					Check_Object(particle);

					//
					//-----------------------------------------------------------------
					// If the particle is still alive, concatenate into world space and
					// issue the draw command
					//-----------------------------------------------------------------
					//
					if (particle->m_age < 1.0f)
					{
						Stuff::Vector3D direction_in_cloud;
						direction_in_cloud.Subtract(
							camera_in_cloud,
							particle->m_localTranslation
						);
						Stuff::LinearMatrix4D shape_to_cloud;
						shape_to_cloud.BuildRotation(particle->m_localRotation);
						shape_to_cloud.AlignLocalAxisToWorldVector(
							direction_in_cloud,
							Stuff::Z_Axis,
							Stuff::Y_Axis,
							Stuff::X_Axis
						);
						shape_to_cloud.BuildTranslation(particle->m_localTranslation);
						Stuff::LinearMatrix4D shape_to_world;
						shape_to_world.Multiply(
							shape_to_cloud,
							local_to_world
						);
						dinfo.shapeToWorld = &shape_to_world;
						Stuff::Vector3D
							scale(
								particle->m_scale,
								particle->m_scale,
								particle->m_scale
							);
						dinfo.scaling = &scale;

						particle->m_color.red = spec->m_pRed.ComputeValue(particle->m_age, particle->m_seed);
						particle->m_color.green = spec->m_pGreen.ComputeValue(particle->m_age, particle->m_seed);
						particle->m_color.blue = spec->m_pBlue.ComputeValue(particle->m_age, particle->m_seed);
						particle->m_color.alpha = spec->m_pAlpha.ComputeValue(particle->m_age, particle->m_seed);
						dinfo.paintMeColor = &particle->m_color;
		 				info->m_clipper->DrawScalableShape(&dinfo);
					}
				}
			}

			//
			//-----------------------
			// Handle X-only rotation
			//-----------------------
			//
			else
			{
				//
				//-----------------------------------------
				// Get the camera location into local space
				//-----------------------------------------
				//
				Stuff::Point3D
					camera_in_world(info->m_clipper->GetCameraToWorldMatrix());
				Stuff::Point3D camera_in_cloud;
				camera_in_cloud.MultiplyByInverse(
					camera_in_world,
					local_to_world
				);
				for (unsigned i = 0; i < m_activeParticleCount; i++)
				{
					Particle *particle = GetParticle(i);
					Check_Object(particle);

					//
					//-----------------------------------------------------------------
					// If the particle is still alive, concatenate into world space and
					// issue the draw command
					//-----------------------------------------------------------------
					//
					if (particle->m_age < 1.0f)
					{
						Stuff::Vector3D direction_in_cloud;
						direction_in_cloud.Subtract(
							camera_in_cloud,
							particle->m_localTranslation
						);
						Stuff::LinearMatrix4D shape_to_cloud;
						shape_to_cloud.BuildRotation(particle->m_localRotation);
						shape_to_cloud.AlignLocalAxisToWorldVector(
							direction_in_cloud,
							Stuff::Z_Axis,
							Stuff::X_Axis,
							-1
						);
						shape_to_cloud.BuildTranslation(particle->m_localTranslation);
						Stuff::LinearMatrix4D shape_to_world;
						shape_to_world.Multiply(
							shape_to_cloud,
							local_to_world
						);
						dinfo.shapeToWorld = &shape_to_world;
						Stuff::Vector3D
							scale(
								particle->m_scale,
								particle->m_scale,
								particle->m_scale
							);
						dinfo.scaling = &scale;

						particle->m_color.red = spec->m_pRed.ComputeValue(particle->m_age, particle->m_seed);
						particle->m_color.green = spec->m_pGreen.ComputeValue(particle->m_age, particle->m_seed);
						particle->m_color.blue = spec->m_pBlue.ComputeValue(particle->m_age, particle->m_seed);
						particle->m_color.alpha = spec->m_pAlpha.ComputeValue(particle->m_age, particle->m_seed);
						dinfo.paintMeColor = &particle->m_color;
		 				info->m_clipper->DrawScalableShape(&dinfo);
					}
				}
			}
		}

		//
		//-------------------------------------------------------
		// Each matrix needs to be aligned to the camera around Y
		//-------------------------------------------------------
		//
		else if (spec->m_alignZUsingY)
		{
			//
			//-----------------------------------------
			// Get the camera location into local space
			//-----------------------------------------
			//
			Stuff::Point3D
				camera_in_world(info->m_clipper->GetCameraToWorldMatrix());
			Stuff::Point3D camera_in_cloud;
			camera_in_cloud.MultiplyByInverse(
				camera_in_world,
				local_to_world
			);
			for (unsigned i = 0; i < m_activeParticleCount; i++)
			{
				Particle *particle = GetParticle(i);
				Check_Object(particle);

				//
				//-----------------------------------------------------------------
				// If the particle is still alive, concatenate into world space and
				// issue the draw command
				//-----------------------------------------------------------------
				//
				if (particle->m_age < 1.0f)
				{
					Stuff::Vector3D direction_in_cloud;
					direction_in_cloud.Subtract(
						camera_in_cloud,
						particle->m_localTranslation
					);
					Stuff::LinearMatrix4D shape_to_cloud;
					shape_to_cloud.BuildRotation(particle->m_localRotation);
					shape_to_cloud.AlignLocalAxisToWorldVector(
						direction_in_cloud,
						Stuff::Z_Axis,
						Stuff::Y_Axis,
						-1
					);
					shape_to_cloud.BuildTranslation(particle->m_localTranslation);
					Stuff::LinearMatrix4D shape_to_world;
					shape_to_world.Multiply(
						shape_to_cloud,
						local_to_world
					);
					dinfo.shapeToWorld = &shape_to_world;
					Stuff::Vector3D
						scale(
							particle->m_scale,
							particle->m_scale,
							particle->m_scale
						);
					dinfo.scaling = &scale;

					particle->m_color.red = spec->m_pRed.ComputeValue(particle->m_age, particle->m_seed);
					particle->m_color.green = spec->m_pGreen.ComputeValue(particle->m_age, particle->m_seed);
					particle->m_color.blue = spec->m_pBlue.ComputeValue(particle->m_age, particle->m_seed);
					particle->m_color.alpha = spec->m_pAlpha.ComputeValue(particle->m_age, particle->m_seed);
					dinfo.paintMeColor = &particle->m_color;
		 			info->m_clipper->DrawScalableShape(&dinfo);
				}
			}
		}

		//
		//---------------------------------------------------------------
		// No alignment is necessary, so just multiply out all the active
		// particles
		//---------------------------------------------------------------
		//
		else
		{
			for (i=0; i < m_activeParticleCount; i++)
			{
				Particle *particle = GetParticle(i);
				Check_Object(particle);

				//
				//-----------------------------------------------------------------
				// If the particle is still alive, concatenate into world space and
				// issue the draw command
				//-----------------------------------------------------------------
				//
				if (particle->m_age < 1.0f)
				{
					Stuff::LinearMatrix4D shape_to_cloud;
					shape_to_cloud.BuildTranslation(particle->m_localTranslation);
					shape_to_cloud.BuildRotation(particle->m_localRotation);
					Stuff::LinearMatrix4D shape_to_world;
					shape_to_world.Multiply(
						shape_to_cloud,
						local_to_world
					);
					dinfo.shapeToWorld = &shape_to_world;
					Stuff::Vector3D
						scale(
							particle->m_scale,
							particle->m_scale,
							particle->m_scale
						);
					dinfo.scaling = &scale;

					particle->m_color.red = spec->m_pRed.ComputeValue(particle->m_age, particle->m_seed);
					particle->m_color.green = spec->m_pGreen.ComputeValue(particle->m_age, particle->m_seed);
					particle->m_color.blue = spec->m_pBlue.ComputeValue(particle->m_age, particle->m_seed);
					particle->m_color.alpha = spec->m_pAlpha.ComputeValue(particle->m_age, particle->m_seed);
					dinfo.paintMeColor = &particle->m_color;
		 			info->m_clipper->DrawScalableShape(&dinfo);
				}
			}
		}
	}

	//
	//----------------------------
	// Let our parent do its thing
	//----------------------------
	//
	BaseClass::Draw(info);
}

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