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

//==========================================================================//
// File:	 gosFX_PointCloud.cpp											//
// Project:  gosFX					                                        //
// Contents: Base gosFX::PointCloud 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::PointCloud__Specification  #############################
//############################################################################

//------------------------------------------------------------------------------
//
gosFX::PointCloud__Specification::PointCloud__Specification(
	Stuff::RegisteredClass::ClassID class_id,
	Stuff::MemoryStream *stream,
	int gfx_version
):
	ParticleCloud__Specification(class_id, stream, gfx_version)
{
	Check_Pointer(this);
	Verify(m_class == PointCloudClassID);
	m_totalParticleSize = gosFX::PointCloud::ParticleSize;
	m_particleClassSize = sizeof(gosFX::PointCloud::Particle);
}

//------------------------------------------------------------------------------
//
gosFX::PointCloud__Specification::PointCloud__Specification(
	Stuff::RegisteredClass::ClassID class_id
):
	ParticleCloud__Specification(class_id)
{
	Check_Pointer(this);
	m_totalParticleSize = gosFX::PointCloud::ParticleSize;
	m_particleClassSize = sizeof(gosFX::PointCloud::Particle);
}

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

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

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

	return spec;
}

//------------------------------------------------------------------------------
//
bool 
	gosFX::PointCloud__Specification::IsDataValid(bool fix_data)
{
	Check_Object(this);

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

	return BaseClass::IsDataValid(fix_data);
}

//############################################################################
//##############################  gosFX::PointCloud  ################################
//############################################################################

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

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

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

//------------------------------------------------------------------------------
//
gosFX::PointCloud::PointCloud(
	ClassData *class_data,
	Specification *spec,
	unsigned flags
):
	ParticleCloud(class_data, spec, flags)
{
	Check_Object(spec);

	gos_PushCurrentHeap(MidLevelRenderer::EffectHeap);
	m_cloudImplementation =
		new MidLevelRenderer::MLRPointCloud(spec->m_maxParticleCount);
	Check_Object(m_cloudImplementation);
	gos_PopCurrentHeap();

	unsigned index = spec->m_maxParticleCount*sizeof(Particle);
	m_P_localTranslation = Cast_Pointer(Stuff::Point3D*, &m_data[index]);
	index += spec->m_maxParticleCount*sizeof(Stuff::Point3D);
	m_P_color = Cast_Pointer(Stuff::RGBAColor*, &m_data[index]);

	m_cloudImplementation->SetData(
		Cast_Pointer(const int *, &m_activeParticleCount),
		m_P_localTranslation,
		m_P_color
	);
}

//------------------------------------------------------------------------------
//
gosFX::PointCloud::~PointCloud()
{
	Check_Object(this);
	Check_Object(m_cloudImplementation);
	delete m_cloudImplementation;
}

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

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

	return cloud;
}

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

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

	//
	//------------------------------------------------------------------
	// Animate the particles.  If it is time for us to die, return false
	//------------------------------------------------------------------
	//
	if (!BaseClass::Execute(info))
		return false;

	//
	//-----------------------------------------------------------------------
	// If there are active particles to animate, get the current center point
	// of the bounds
	//-----------------------------------------------------------------------
	//
	if (m_activeParticleCount > 0)
	{
		GFX_LOGIC("Execute::ComputeBounds::PointCloud");
		Stuff::ExtentBox box(Stuff::Point3D::Identity, Stuff::Point3D::Identity);
		Stuff::Point3D *vertex = &m_P_localTranslation[0];
		unsigned i=0;

		//
		//-------------------------------------------------------------------
		// If there is no bounds yet, we need to create our extent box around
		// the first legal point we find
		//-------------------------------------------------------------------
		//
		while (i<m_activeParticleCount)
		{
			Particle *particle = GetParticle(i);
			Check_Object(particle);
			if (particle->m_age < 1.0f)
			{
				Check_Object(vertex);
				box.maxX = vertex->x;
				box.minX = vertex->x;
				box.maxY = vertex->y;
				box.minY = vertex->y;
				box.maxZ = vertex->z;
				box.minZ = vertex->z;
				++vertex;
				++i;
				break;
			}
			++vertex;
			++i;
		}

		//
		//-----------------------------
		// Look for the other particles
		//-----------------------------
		//
		while (i<m_activeParticleCount)
		{
			Particle *particle = GetParticle(i);
			Check_Object(particle);
			if (particle->m_age < 1.0f)
			{
				Check_Object(vertex);
				if (vertex->x > box.maxX)
					box.maxX = vertex->x;
				else if (vertex->x < box.minX)
					box.minX = vertex->x;

				if (vertex->y > box.maxY)
					box.maxY = vertex->y;
				else if (vertex->y < box.minY)
					box.minY = vertex->y;

				if (vertex->z > box.maxZ)
					box.maxZ = vertex->z;
				else if (vertex->z < box.minZ)
					box.minZ = vertex->z;
			}
			++vertex;
			++i;
		}

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

	//
	//----------------------------------------------
	// Tell our caller that we get to keep executing
	//----------------------------------------------
	//
	return true;
}

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

	//
	//-----------------------------------------------------------------------
	// Let our parent do creation, then turn on the particle in the cloud and
	// set its position
	//-----------------------------------------------------------------------
	//
	BaseClass::CreateNewParticle(index, translation);
	m_cloudImplementation->TurnOn(index);
	Verify(m_cloudImplementation->IsOn(index));
	m_P_localTranslation[index] = *translation;
}

//------------------------------------------------------------------------------
//
bool
	gosFX::PointCloud::AnimateParticle(
		unsigned index,
		const Stuff::LinearMatrix4D *world_to_new_local,
		Stuff::Time till
	)
{
	Check_Object(this);
	PARTICLE_LOGIC("Point");

	//
	//-----------------------------------------------------------------------
	// If this cloud is unparented, we need to transform the point from local
	// space into world space and set the internal position/velocity pointers
	// to these temporary values
	//-----------------------------------------------------------------------
	//
	Particle *particle = GetParticle(index);
	Check_Object(particle);
	Stuff::Scalar age = particle->m_age;
	if (age >= 1.0f)
		return false;
	Set_Statistic(Point_Count, Point_Count+1);
	Stuff::Vector3D *velocity = &particle->m_localLinearVelocity;
	Stuff::Point3D *translation = &m_P_localTranslation[index];
	int sim_mode = GetSimulationMode();
	if (sim_mode == DynamicWorldSpaceSimulationMode)
	{
		Check_Object(translation);
		Check_Object(velocity);
		particle->m_worldLinearVelocity.Multiply(*velocity, m_localToWorld);
		particle->m_worldTranslation.Multiply(*translation, m_localToWorld);
		translation = &particle->m_worldTranslation;
		velocity = &particle->m_worldLinearVelocity;
	}
	Check_Object(translation);
	Check_Object(velocity);

	//
	//------------------------------------------------------------------
	// First, calculate the drag on the particle.  Drag can never assist
	// velocity
	//------------------------------------------------------------------
	//
	Stuff::Scalar seed = particle->m_seed;
	Specification *spec = GetSpecification();
	Check_Object(spec);
	Stuff::Vector3D ether;
	ether.x = spec->m_pEtherVelocityX.ComputeValue(age, seed);
	ether.y = spec->m_pEtherVelocityY.ComputeValue(age, seed);
	ether.z = spec->m_pEtherVelocityZ.ComputeValue(age, seed);
	Stuff::Vector3D accel(Stuff::Vector3D::Identity);

	//
	//-------------------------------------------------------------------
	// Deal with pseudo-world simulation.  In this mode, we interpret the
	// forces as if they are already in worldspace, and we transform them
	// back to local space
	//-------------------------------------------------------------------
	//
	Stuff::Scalar drag = -spec->m_pDrag.ComputeValue(age, seed);
	Max_Clamp(drag, 0.0f);
	if (sim_mode == StaticWorldSpaceSimulationMode)
	{
		Stuff::LinearMatrix4D world_to_effect;
		world_to_effect.Invert(m_localToWorld);
		Stuff::Vector3D local_ether;
		local_ether.MultiplyByInverse(ether, world_to_effect);
		Stuff::Vector3D rel_vel;
		rel_vel.Subtract(*velocity, local_ether);
		accel.Multiply(rel_vel, drag);

		//
		//-----------------------------------------
		// Now, add in acceleration of the particle
		//-----------------------------------------
		//
		Stuff::Vector3D world_accel;
		world_accel.x = spec->m_pAccelerationX.ComputeValue(age, seed);
		world_accel.y = spec->m_pAccelerationY.ComputeValue(age, seed);
		world_accel.z = spec->m_pAccelerationZ.ComputeValue(age, seed);
		Stuff::Vector3D local_accel;
		local_accel.Multiply(world_accel, world_to_effect);
		accel += local_accel;
	}

	//
	//----------------------------------------------------------------------
	// Otherwise, just add the forces in the same space the particles are in
	//----------------------------------------------------------------------
	//
	else
	{
		Stuff::Vector3D rel_vel;
		rel_vel.Subtract(*velocity, ether);
		accel.Multiply(rel_vel, drag);

		//
		//-----------------------------------------
		// Now, add in acceleration of the particle
		//-----------------------------------------
		//
		accel.x += spec->m_pAccelerationX.ComputeValue(age, seed);
		accel.y += spec->m_pAccelerationY.ComputeValue(age, seed);
		accel.z += spec->m_pAccelerationZ.ComputeValue(age, seed);
	}

	//
	//-------------------------------------------------
	// Compute the particle's new velocity and position
	//-------------------------------------------------
	//
	Stuff::Scalar time_slice =
		static_cast<Stuff::Scalar>(till - m_lastRan);
	velocity->AddScaled(*velocity, accel, time_slice);
	translation->AddScaled(*translation, *velocity, time_slice);

	//
	//---------------------------------------------------------------------
	// If we are unparented, we need to transform the velocity and position
	// data back into the NEW local space
	//---------------------------------------------------------------------
	//
	if (sim_mode == DynamicWorldSpaceSimulationMode)
	{
		Check_Object(world_to_new_local);
		particle->m_localLinearVelocity.Multiply(
			particle->m_worldLinearVelocity,
			*world_to_new_local
		);
		m_P_localTranslation[index].Multiply(
			particle->m_worldTranslation,
			*world_to_new_local
		);
	}
	return true;
}

//------------------------------------------------------------------------------
//
void gosFX::PointCloud::DestroyParticle(unsigned index)
{
	Check_Object(this);

	m_cloudImplementation->TurnOff(index);
	Verify(!m_cloudImplementation->IsOn(index));
	BaseClass::DestroyParticle(index);
}

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

	//
	//-----------------------------------------------------------------
	// If there are particles to draw, first update the particle colors
	//-----------------------------------------------------------------
	//
	if (m_activeParticleCount)
	{
		Specification *spec = GetSpecification();
		Check_Object(spec);
		Check_Pointer(m_P_color);
		for (unsigned i=0; i<m_activeParticleCount; ++i)
		{
			Particle *particle = GetParticle(i);
			Check_Object(particle);
			Stuff::Scalar age = particle->m_age;
			if (age < 1.0f)
			{
				Stuff::Scalar seed = particle->m_seed;
				m_P_color[i].red = spec->m_pRed.ComputeValue(age, seed);
				m_P_color[i].green = spec->m_pGreen.ComputeValue(age, seed);
				m_P_color[i].blue = spec->m_pBlue.ComputeValue(age, seed);
				m_P_color[i].alpha = spec->m_pAlpha.ComputeValue(age, seed);
			}
		}

		//
		//---------------------
		// Now actually draw it
		//---------------------
		//
		MidLevelRenderer::DrawEffectInformation dInfo;
		dInfo.effect = m_cloudImplementation;
		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;
	
	 	info->m_clipper->DrawEffect(&dInfo);
	}
	BaseClass::Draw(info);
}

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