#include "gosFXHeaders.hpp"

//==========================================================================//
// File:	 gosFX_Effect.cpp												//
// Project:  gosFX					                                        //
// Contents: Base gosFX::Effect 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              //
//==========================================================================//
//

//############################################################################
//########################  EffectSpecification  #############################
//############################################################################

//------------------------------------------------------------------------------
//
gosFX::Event::Event(const Event& event):
	Plug(DefaultData)
{
	Check_Pointer(this);

	m_time = event.m_time;
	m_flags = event.m_flags;
	m_effectID = event.m_effectID;
	m_nearLimit = event.m_nearLimit;
	m_farLimit = event.m_farLimit;
}

//------------------------------------------------------------------------------
//
gosFX::Event::Event(
	Stuff::MemoryStream *stream,
	int gfx_version
):
	Plug(DefaultData)
{
	Check_Pointer(this);
	Check_Object(stream);

	*stream >> m_time >> m_flags;
	if (gfx_version >= 21)
	{
		*stream >> m_localToParent;
		Stuff::MString name;
		*stream >> name;
		Check_Object(EffectLibrary::Instance);
		int id = EffectLibrary::Instance->Find(name);
		Verify(id >= 0);
		m_effectID = id;
	}
	else
		*stream >> m_effectID >> m_localToParent;
	if (gfx_version >= 19)
		*stream >> m_nearLimit >> m_farLimit;
	else
	{
		m_nearLimit = 0.0f;
		m_farLimit = 6.4e6f;
	}
}

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

	gos_PushCurrentHeap(Heap);
	Event *event = new Event(stream, gfx_version);
	gos_PopCurrentHeap();

	return event;
}

//------------------------------------------------------------------------------
//
void
	gosFX::Event::Save(Stuff::MemoryStream *stream)
{
	Check_Object(this);
	*stream << m_time << m_flags << m_localToParent;
	Check_Object(EffectLibrary::Instance);
	Effect::Specification *effect = EffectLibrary::Instance->Find(m_effectID);
	Check_Object(effect);
	*stream << effect->m_name << m_nearLimit << m_farLimit;
}

//############################################################################
//########################  EffectSpecification  #############################
//############################################################################

#if defined(LAB_ONLY)
	bool gosFX::Effect__Specification::s_DeleteIsOK = false;
#endif

//------------------------------------------------------------------------------
//
gosFX::Effect__Specification::Effect__Specification(
	Stuff::RegisteredClass::ClassID class_id,
	Stuff::MemoryStream *stream,
	int gfx_version
):
	m_events(NULL)
{
	Check_Pointer(this);
	Check_Object(stream);

	if (gfx_version < 9)
	{
		STOP(("This version of gosFX is no longer supported"));
		return;
	}

	//
	//--------------
	// Read the name
	//--------------
	//
	m_class = class_id;
	*stream >> m_name;

	//
	//--------------------------------------------------------------------
	// Read the events.  If we are using an array, no events will be saved
	//--------------------------------------------------------------------
	//
	unsigned event_count;
	*stream >> event_count;
	while (event_count-- > 0)
	{
		Event *event = Event::Make(stream, gfx_version);
		Check_Object(event);
		m_events.Add(event);
	}

	//
	//--------------------------------------------------------
	// Load the curves, variances, and MLRState if appropriate
	//--------------------------------------------------------
	//
	m_lifeSpan.Load(stream, gfx_version);
	m_minimumChildSeed.Load(stream, gfx_version);
	m_maximumChildSeed.Load(stream, gfx_version);
	m_state.Load(stream, MidLevelRenderer::ReadMLRVersion(stream));
	if (gfx_version < 14)
	{
		m_state.SetRenderPermissionMask(
			m_state.GetRenderPermissionMask() | MidLevelRenderer::MLRState::TextureMask
		);
	}
	if (
		m_state.GetAlphaMode() != MidLevelRenderer::MLRState::OneZeroMode
		&& m_state.GetPriority() < MidLevelRenderer::MLRState::AlphaPriority
	)
		m_state.SetPriority(MidLevelRenderer::MLRState::AlphaPriority);
}

//------------------------------------------------------------------------------
//
gosFX::Effect__Specification::Effect__Specification(
	Stuff::RegisteredClass::ClassID class_id
):
	m_events(NULL)
{
	Check_Pointer(this);
	m_class = class_id;
}

//------------------------------------------------------------------------------
//
gosFX::Effect__Specification::~Effect__Specification()
{
	Check_Object(this);

	#if defined(LAB_ONLY)
		if (!s_DeleteIsOK)
			STOP(("An Effect specification is being illegally deleted"));
	#endif

	Verify(m_class >= Stuff::FirstgosFXClassID && m_class < FirstFreegosFXClassID);
	m_events.DeletePlugs();
}

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

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

	return spec;
}

//------------------------------------------------------------------------------
//

void 
	gosFX::Effect__Specification::BuildDefaults()
{

	Check_Object(this);
	
	m_lifeSpan.SetCurve(1.0f);
	m_minimumChildSeed.SetCurve(0.0f);
	m_maximumChildSeed.SetCurve(1.0f);
}


//------------------------------------------------------------------------------
//

bool 
	gosFX::Effect__Specification::IsDataValid(bool fix_data)
{
	Check_Object(this);
	Stuff::Scalar minv,maxv;
	m_lifeSpan.ExpensiveComputeRange(&minv,&maxv);
	if(minv<0.0f) 
	{
		if(fix_data)
		{
			m_lifeSpan.SetCurve(1.0f);
			PAUSE(("Warning: Curve \"lifespan\" in Effect \"%s\" Is Out of Range and has been Reset",(char *)m_name));
		}
		else
			return false;
	}
	return true;
}

//------------------------------------------------------------------------------
//
gosFX::Effect__Specification*
	gosFX::Effect__Specification::Create(
		Stuff::MemoryStream *stream,
		int gfx_version
	)
{
	Check_Object(stream);
	Stuff::RegisteredClass::ClassID class_id;
	*stream >> class_id;
	gosFX::Effect::ClassData* class_data =
		Cast_Pointer(
			gosFX::Effect::ClassData*,
			Stuff::RegisteredClass::FindClassData(class_id)
		);
	Check_Object(class_data);
	Check_Pointer(class_data->specificationFactory);
	gosFX::Effect__Specification *spec =
		(*class_data->specificationFactory)(stream, gfx_version);
	Check_Object(spec);
	return spec;
}

//------------------------------------------------------------------------------
//
void
	gosFX::Effect__Specification::Save(Stuff::MemoryStream *stream)
{
	Check_Object(this);
	*stream << m_class << m_name;
	Stuff::ChainIteratorOf<Event*> events(&m_events);
	unsigned count = events.GetSize();
	*stream << count;
	Event *event;
	while ((event = events.ReadAndNext()) != NULL)
	{
		Check_Object(event);
		event->Save(stream);
	}
	m_lifeSpan.Save(stream);
	m_minimumChildSeed.Save(stream);
	m_maximumChildSeed.Save(stream);
	MidLevelRenderer::WriteMLRVersion(stream);
	m_state.Save(stream);
}

//------------------------------------------------------------------------------
//
void
	gosFX::Effect__Specification::Copy(Effect__Specification *spec)
{
	Check_Object(this);
	Check_Object(spec);
	gos_PushCurrentHeap(Heap);

	Verify(spec->m_class == m_class);
	m_name = spec->m_name;

	//
	//----------------------------------------------
	// Copy the events after delete our current ones
	//----------------------------------------------
	//
	m_events.DeletePlugs();
	Stuff::ChainIteratorOf<Event*> new_events(&spec->m_events);
	Event* event;
	while ((event = new_events.ReadAndNext()) != NULL)
	{
		Check_Object(event);
		Event *new_event = new Event(*event);
		Check_Object(new_event);
		AdoptEvent(new_event);
	}

	//
	//---------------------------------------------------
	// Now copy the curves, variance modes, and MLR state
	//---------------------------------------------------
	//
	m_lifeSpan = spec->m_lifeSpan;
	m_minimumChildSeed = spec->m_minimumChildSeed;
	m_maximumChildSeed = spec->m_maximumChildSeed;
	m_state = spec->m_state;

	gos_PopCurrentHeap();
}

//------------------------------------------------------------------------------
//
void
	gosFX::Effect__Specification::AdoptEvent(Event *event)
{
	Check_Object(this);
	Check_Object(event);
	Verify(event->m_time >= 0.0f && event->m_time <= 1.0f);

	//
	//-----------------------------------------------------------
	// The event must be inserted into the chain in order of time
	//-----------------------------------------------------------
	//
	Stuff::ChainIteratorOf<Event*> events(&m_events);
	Event *insert = NULL;
	while ((insert = events.GetCurrent()) != NULL)
	{
		Check_Object(insert);
		if (insert->m_time > event->m_time)
		{
			events.Insert(event);
			return;
		}
		events.Next();
	}
	m_events.Add(event);
}

//------------------------------------------------------------------------------
//
void gosFX::Effect__Specification::TestInstance() const
{
	Verify(m_class >= Stuff::FirstgosFXClassID && m_class < FirstFreegosFXClassID);
}

//############################################################################
//##############################   gosFX::Effect  ###################################
//############################################################################

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

Stuff::Scalar
	gosFX::Effect::LODOffset = 0.0f;

Stuff::Point3D
	gosFX::Effect::LastCameraPosition;

static DWORD
	Effect_Count;

static bool
	__stdcall Check100m()
{
	return gosFX::Effect::LODOffset == 10000.0f;
}

static bool
	__stdcall Check250m()
{
	return gosFX::Effect::LODOffset == 62500.0f;
}

static bool
	__stdcall Check500m()
{
	return gosFX::Effect::LODOffset == 250000.0f;
}

static void
	__stdcall Enable100m()
{
	gosFX::Effect::LODOffset = (gosFX::Effect::LODOffset == 10000.0f) ? 0.0f : 10000.0f;
}

static void
	__stdcall Enable250m()
{
	gosFX::Effect::LODOffset = (gosFX::Effect::LODOffset == 62500.0f) ? 0.0f : 62500.0f;
}

static void
	__stdcall Enable500m()
{
	gosFX::Effect::LODOffset = (gosFX::Effect::LODOffset == 250000.0f) ? 0.0f : 250000.0f;
}

static bool
	Disabled_Events[gosFX::FirstFreegosFXClassID - gosFX::EffectClassID]=
{
	false,false,false,false,false,false,
	false,false,false,false,false,false,
	false,false,false,false,false,false
};

static bool
	__stdcall CheckAllEvents()
{
	for (int i=0; i<ELEMENTS(Disabled_Events); ++i)
	{
		if (!Disabled_Events[i])
			return false;
	}
	return true;
}

static void
	__stdcall EnableAllEvents()
{
	bool checked = !CheckAllEvents();
	for (int i=0; i<ELEMENTS(Disabled_Events); ++i)
		Disabled_Events[i] = checked;
}

static bool
	__stdcall CheckEffectEvents()
{
	return Disabled_Events[0];
}

static void
	__stdcall EnableEffectEvents()
{
	Disabled_Events[0] = !Disabled_Events[0];
}

static bool
	__stdcall CheckPointCloudEvents()
{
	return Disabled_Events[gosFX::PointCloudClassID-gosFX::EffectClassID];
}

static void
	__stdcall EnablePointCloudEvents()
{
	Disabled_Events[gosFX::PointCloudClassID-gosFX::EffectClassID] =
		!Disabled_Events[gosFX::PointCloudClassID-gosFX::EffectClassID];
}

static bool
	__stdcall CheckShardCloudEvents()
{
	return Disabled_Events[gosFX::ShardCloudClassID-gosFX::EffectClassID];
}

static void
	__stdcall EnableShardCloudEvents()
{
	Disabled_Events[gosFX::ShardCloudClassID-gosFX::EffectClassID] =
		!Disabled_Events[gosFX::ShardCloudClassID-gosFX::EffectClassID];
}

static bool
	__stdcall CheckPertCloudEvents()
{
	return Disabled_Events[gosFX::PertCloudClassID-gosFX::EffectClassID];
}

static void
	__stdcall EnablePertCloudEvents()
{
	Disabled_Events[gosFX::PertCloudClassID-gosFX::EffectClassID] =
		!Disabled_Events[gosFX::PertCloudClassID-gosFX::EffectClassID];
}

static bool
	__stdcall CheckCardCloudEvents()
{
	return Disabled_Events[gosFX::CardCloudClassID-gosFX::EffectClassID];
}

static void
	__stdcall EnableCardCloudEvents()
{
	Disabled_Events[gosFX::CardCloudClassID-gosFX::EffectClassID] =
		!Disabled_Events[gosFX::CardCloudClassID-gosFX::EffectClassID];
}

static bool
	__stdcall CheckShapeCloudEvents()
{
	return Disabled_Events[gosFX::ShapeCloudClassID-gosFX::EffectClassID];
}

static void
	__stdcall EnableShapeCloudEvents()
{
	Disabled_Events[gosFX::ShapeCloudClassID-gosFX::EffectClassID] =
		!Disabled_Events[gosFX::ShapeCloudClassID-gosFX::EffectClassID];
}

static bool
	__stdcall CheckEffectCloudEvents()
{
	return Disabled_Events[gosFX::EffectCloudClassID-gosFX::EffectClassID];
}

static void
	__stdcall EnableEffectCloudEvents()
{
	Disabled_Events[gosFX::EffectCloudClassID-gosFX::EffectClassID] =
		!Disabled_Events[gosFX::EffectCloudClassID-gosFX::EffectClassID];
}

static bool
	__stdcall CheckCardEvents()
{
	return Disabled_Events[gosFX::CardClassID-gosFX::EffectClassID];
}

static void
	__stdcall EnableCardEvents()
{
	Disabled_Events[gosFX::CardClassID-gosFX::EffectClassID] =
		!Disabled_Events[gosFX::CardClassID-gosFX::EffectClassID];
}

static bool
	__stdcall CheckShapeEvents()
{
	return Disabled_Events[gosFX::ShapeClassID-gosFX::EffectClassID];
}

static void
	__stdcall EnableShapeEvents()
{
	Disabled_Events[gosFX::ShapeClassID-gosFX::EffectClassID] =
		!Disabled_Events[gosFX::ShapeClassID-gosFX::EffectClassID];
}

static bool
	__stdcall CheckTubeEvents()
{
	return Disabled_Events[gosFX::TubeClassID-gosFX::EffectClassID];
}

static void
	__stdcall EnableTubeEvents()
{
	Disabled_Events[gosFX::TubeClassID-gosFX::EffectClassID] =
		!Disabled_Events[gosFX::TubeClassID-gosFX::EffectClassID];
}

static bool
	__stdcall CheckDebrisCloudEvents()
{
	return Disabled_Events[gosFX::DebrisCloudClassID-gosFX::EffectClassID];
}

static void
	__stdcall EnableDebrisCloudEvents()
{
	Disabled_Events[gosFX::DebrisCloudClassID-gosFX::EffectClassID] =
		!Disabled_Events[gosFX::DebrisCloudClassID-gosFX::EffectClassID];
}

static bool
	__stdcall CheckPointLightEvents()
{
	return Disabled_Events[gosFX::PointLightClassID-gosFX::EffectClassID];
}

static void
	__stdcall EnablePointLightEvents()
{
	Disabled_Events[gosFX::PointLightClassID-gosFX::EffectClassID] =
		!Disabled_Events[gosFX::PointLightClassID-gosFX::EffectClassID];
}

static bool
	__stdcall CheckFlareEvents()
{
	return Disabled_Events[gosFX::FlareClassID-gosFX::EffectClassID];
}

static void
	__stdcall EnableFlareEvents()
{
	Disabled_Events[gosFX::FlareClassID-gosFX::EffectClassID] =
		!Disabled_Events[gosFX::FlareClassID-gosFX::EffectClassID];
}

static bool
	__stdcall CheckSpriteCloudEvents()
{
	return Disabled_Events[gosFX::SpriteCloudClassID-gosFX::EffectClassID];
}

static void
	__stdcall EnableSpriteCloudEvents()
{
	Disabled_Events[gosFX::SpriteCloudClassID-gosFX::EffectClassID] =
		!Disabled_Events[gosFX::SpriteCloudClassID-gosFX::EffectClassID];
}

//------------------------------------------------------------------------------
//
void
	gosFX::Effect::InitializeClass(Stuff::NotationFile *startup_ini)
{
	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			EffectClassID,
			"gosFX::Effect",
			BaseClass::DefaultData,
			&Make,
			&Specification::Make
		);
	Check_Object(DefaultData);

	LODOffset = 0.0f;
	Effect_Count = 0;

#if !defined(NO_STATS)
	AddStatistic("Effects Executed", "effects", gos_DWORD, &Effect_Count, Stat_AutoReset);
#endif

	AddDebuggerMenuItem("Libraries\\Graphics Options\\+100m Effect LODs", Check100m, Enable100m, 0 );
	AddDebuggerMenuItem("Libraries\\Graphics Options\\+250m Effect LODs", Check250m, Enable250m, 0 );
	AddDebuggerMenuItem("Libraries\\Graphics Options\\+500m Effect LODs", Check500m, Enable500m, 0 );

	//
	//-------------------------------------------
	// Read the effect LOD from the notation file
	//-------------------------------------------
	//
	if (startup_ini)
	{
		Check_Object(startup_ini);
		Stuff::Page *page = startup_ini->FindPage("Graphics Options");
		if (page)
		{
			Check_Object(page);
			page->GetEntry("EffectLOD", &LODOffset);
		}
	}

	AddDebuggerMenuItem("Libraries\\gosFX\\Disable All Events", CheckAllEvents, EnableAllEvents, 0 );
	AddDebuggerMenuItem("Libraries\\gosFX\\Disable Null Events", CheckEffectEvents, EnableEffectEvents, 0 );
	AddDebuggerMenuItem("Libraries\\gosFX\\Disable PointCloud Events", CheckPointCloudEvents, EnablePointCloudEvents, 0 );
	AddDebuggerMenuItem("Libraries\\gosFX\\Disable ShardCloud Events", CheckShardCloudEvents, EnableShardCloudEvents, 0 );
	AddDebuggerMenuItem("Libraries\\gosFX\\Disable PertCloud Events", CheckPertCloudEvents, EnablePertCloudEvents, 0 );
	AddDebuggerMenuItem("Libraries\\gosFX\\Disable CardCloud Events", CheckCardCloudEvents, EnableCardCloudEvents, 0 );
	AddDebuggerMenuItem("Libraries\\gosFX\\Disable ShapeCloud Events", CheckShapeCloudEvents, EnableShapeCloudEvents, 0 );
	AddDebuggerMenuItem("Libraries\\gosFX\\Disable EffectCloud Events", CheckEffectCloudEvents, EnableEffectCloudEvents, 0 );
	AddDebuggerMenuItem("Libraries\\gosFX\\Disable Card Events", CheckCardEvents, EnableCardEvents, 0 );
	AddDebuggerMenuItem("Libraries\\gosFX\\Disable Shape Events", CheckShapeEvents, EnableShapeEvents, 0 );
	AddDebuggerMenuItem("Libraries\\gosFX\\Disable Tube Events", CheckTubeEvents, EnableTubeEvents, 0 );
	AddDebuggerMenuItem("Libraries\\gosFX\\Disable DebrisCloud Events", CheckDebrisCloudEvents, EnableDebrisCloudEvents, 0 );
	AddDebuggerMenuItem("Libraries\\gosFX\\Disable PointLight Events", CheckPointLightEvents, EnablePointLightEvents, 0 );
	AddDebuggerMenuItem("Libraries\\gosFX\\Disable Flare Events", CheckFlareEvents, EnableFlareEvents, 0 );
	AddDebuggerMenuItem("Libraries\\gosFX\\Disable SpriteCloud Events", CheckSpriteCloudEvents, EnableSpriteCloudEvents, 0 );
}

//------------------------------------------------------------------------------
//
void
	gosFX::Effect::TerminateClass(Stuff::NotationFile *startup_ini)
{
	if (startup_ini)
	{
		Check_Object(startup_ini);
		Stuff::Page *page = startup_ini->SetPage("Graphics Options");
		Check_Object(page);
		page->SetEntry("EffectLOD", LODOffset);
	}

	Check_Object(DefaultData);
	delete DefaultData;
	DefaultData = NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
gosFX::Effect::Effect(
	ClassData *class_data,
	Specification *spec,
	unsigned flags
):
	Plug(class_data),
	m_children(NULL),
	m_event(&spec->m_events)
{
	Check_Pointer(this);
	Check_Object(spec);
	m_specification = spec;
	m_age = 0.0f;
	m_flags = flags;
	m_lastRan = -1.0f;
	m_localToParent = Stuff::LinearMatrix4D::Identity;
}


//------------------------------------------------------------------------------
//
gosFX::Effect::~Effect()
{
	Check_Object(this);
	m_children.DeletePlugs();
}

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

	gos_PushCurrentHeap(Heap);
	Effect *effect = new gosFX::Effect(DefaultData, spec, flags);
	gos_PopCurrentHeap();

	return effect;
}

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

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

	//
	//---------------------------------------------------------------------
	// Don't override m_lastran if we are issuing a Start command while the
	// effect is already running
	//---------------------------------------------------------------------
	//
	if (!IsExecuted() || m_lastRan == -1.0)
		m_lastRan = info->m_time;
	SetExecuteOn();

	//
	//-------------------------------------------
	// If no seed was provided, pick one randomly
	//-------------------------------------------
	//
	m_seed = (info->m_seed == -1.0f) ? Stuff::Random::GetFraction() : info->m_seed;
	Verify(m_seed >= 0.0f && m_seed <= 1.0f);

	//
	//--------------------------------------------------------------------
	// Figure out how long the emitter will live and its initial age based
	// upon the effect seed
	//--------------------------------------------------------------------
	//
	Check_Object(m_specification);
	if (info->m_ageRate == -1.0f)
	{
		Stuff::Scalar lifetime =
			m_specification->m_lifeSpan.ComputeValue(m_seed, 0.0f);
		Min_Clamp(lifetime, 0.033333f);
		m_ageRate = 1.0f / lifetime;
	}
	else
		m_ageRate = info->m_ageRate;
	m_age = info->m_age;
	Verify(m_age >= 0.0f && m_age <= 1.0f);

	//
	//--------------------
	// Set up the matrices
	//--------------------
	//
	Check_Object(info->m_parentToWorld);
	m_localToWorld.Multiply(m_localToParent, *info->m_parentToWorld);

	//
	//-------------------------
	// Set up the event pointer
	//-------------------------
	//
	m_event.First();
#if 0
	Event *event;
	while ((event = m_event.GetCurrent()) != NULL)
	{
		Check_Object(event);
		if (event->m_time > m_age)
			break;

		//
		//---------------------------------------------------------
		// Make sure we only launch the effect if we are allowed to
		//---------------------------------------------------------
		//
		#if defined(LAB_ONLY)
			if (
				Disabled_Events[
					EffectLibrary::Instance->Find(event->m_effectID)->GetClassID()
					 - EffectClassID
				]
			)
			{
				m_event.Next();
				continue;
			}
		#endif

		//
		//------------------------------------------------------
		// Make sure the effect is within our known camera range
		//------------------------------------------------------
		//
		Stuff::Vector3D diff(
			LastCameraPosition.x - m_localToWorld(3,0),
			LastCameraPosition.y - m_localToWorld(3,1),
			LastCameraPosition.z - m_localToWorld(3,2)
		);
		Stuff::Scalar camera_distance = diff.GetLengthSquared() + LODOffset;
		Min_Clamp(camera_distance, LODOffset);
		if (camera_distance < event->m_nearLimit || camera_distance > event->m_farLimit)
		{
			m_event.Next();
			continue;
		}

		//
		//-------------------------------------------------------------
		// This event needs to go, so spawn and bump the effect pointer
		//-------------------------------------------------------------
		//
		GFX_LOGIC("Execute::Effect::CreateChild");
		unsigned flags = ExecuteFlag;
		if ((event->m_flags&SimulationModeMask) == ParentSimulationMode)
		{
			Verify((m_flags&SimulationModeMask) != ParentSimulationMode);
			flags |= m_flags&SimulationModeMask;
		}
		else
			flags |= event->m_flags&SimulationModeMask;

		Effect* effect =
			EffectLibrary::Instance->MakeEffect(
				event->m_effectID,
				flags
			);
		Check_Object(effect);
		m_children.Add(effect);
		m_event.Next();

		//
		//---------------------------------------------
		// Now set the info for starting the new effect
		//---------------------------------------------
		//
		effect->m_localToParent = event->m_localToParent;
		Stuff::Scalar min_seed =
			m_specification->m_minimumChildSeed.ComputeValue(m_age, m_seed);
		Stuff::Scalar seed_range =
			m_specification->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);
		ExecuteInfo
			local_info(
				info->m_time,
				&m_localToWorld,
				NULL,
				seed
			);
		effect->Start(&local_info);
	}
#endif
	gos_PopCurrentHeap();
}

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

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

	Set_Statistic(Effect_Count, Effect_Count+1);
	Verify(IsExecuted());
	gos_PushCurrentHeap(Heap);

	//
	//-----------------------------------------------------
	// If a new seed is provided, override the current seed
	//-----------------------------------------------------
	//
	if (info->m_seed != -1.0f)
	{
		Verify(info->m_seed>=0.0f && info->m_seed<1.0f);
		m_seed = info->m_seed;
	}

	//
	//--------------------------------------------
	// Figure out the new age and clear the bounds
	//--------------------------------------------
	//
	Stuff::Scalar delta = static_cast<Stuff::Scalar>(info->m_time - m_lastRan);
	Stuff::Scalar age = m_age + delta * m_ageRate;
	Verify(age >= 0.0f && age >= m_age);
	*info->m_bounds = Stuff::OBB::Identity;

	//
	//--------------------------------
	// Update the effectToWorld matrix
	//--------------------------------
	//
	Check_Object(info->m_parentToWorld);
	m_localToWorld.Multiply(m_localToParent, *info->m_parentToWorld);

	//
	//--------------------------------------------------
	// Check to see if the top event needs to be handled
	//--------------------------------------------------
	//
	Check_Object(m_specification);
	Event *event;
	while ((event = m_event.GetCurrent()) != NULL)
	{
		Check_Object(event);
		if (event->m_time > m_age)
			break;

		//
		//---------------------------------------------------------
		// Make sure we only launch the effect if we are allowed to
		//---------------------------------------------------------
		//
		#if defined(LAB_ONLY)
			if (
				Disabled_Events[
					EffectLibrary::Instance->Find(event->m_effectID)->GetClassID()
					 - EffectClassID
				]
			)
			{
				m_event.Next();
				continue;
			}
		#endif

		//
		//------------------------------------------------------
		// Make sure the effect is within our known camera range
		//------------------------------------------------------
		//
		Stuff::Vector3D diff(
			LastCameraPosition.x - m_localToWorld(3,0),
			LastCameraPosition.y - m_localToWorld(3,1),
			LastCameraPosition.z - m_localToWorld(3,2)
		);
		Stuff::Scalar camera_distance = diff.GetLengthSquared() + LODOffset;
		Min_Clamp(camera_distance, LODOffset);
		if (camera_distance < event->m_nearLimit || camera_distance > event->m_farLimit)
		{
			m_event.Next();
			continue;
		}

		//
		//-------------------------------------------------------------
		// This event needs to go, so spawn and bump the effect pointer
		//-------------------------------------------------------------
		//
		GFX_LOGIC("Execute::Effect::CreateChild");
		unsigned flags = ExecuteFlag;
		if ((event->m_flags&SimulationModeMask) == ParentSimulationMode)
		{
			Verify((m_flags&SimulationModeMask) != ParentSimulationMode);
			flags |= m_flags&SimulationModeMask;
		}
		else
			flags |= event->m_flags&SimulationModeMask;

		Effect* effect =
			EffectLibrary::Instance->MakeEffect(
				event->m_effectID,
				flags
			);
		Check_Object(effect);
		m_children.Add(effect);
		m_event.Next();

		//
		//---------------------------------------------
		// Now set the info for starting the new effect
		//---------------------------------------------
		//
		effect->m_localToParent = event->m_localToParent;
		Stuff::Scalar min_seed =
			m_specification->m_minimumChildSeed.ComputeValue(m_age, m_seed);
		Stuff::Scalar seed_range =
			m_specification->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);
		ExecuteInfo
			local_info(
				info->m_time,
				&m_localToWorld,
				NULL,
				seed
			);
		effect->Start(&local_info);
	}

	//
	//------------------------------------------------------------
	// Execute all the children.  If any of them finish, kill them
	//------------------------------------------------------------
	//
	{
		GFX_LOGIC("Execute::Effect::ExecuteChildren");
		Stuff::ChainIteratorOf<gosFX::Effect*> children(&m_children);
		gosFX::Effect *child;
		Stuff::OBB child_obb = Stuff::OBB::Identity;
		ExecuteInfo
			child_info(
				info->m_time,
				&m_localToWorld,
				&child_obb
			);
		child_info.m_bounds = &child_obb;
		while ((child = children.ReadAndNext()) != NULL)
		{
			Check_Object(child);
			if (!child->Execute(&child_info))
			{
				Check_Object(child);
				delete child;
				continue;
			}

			//
			//--------------------------------------------------------------
			// Merge the bounding sphere of the child into the bounds of the
			// parent
			//--------------------------------------------------------------
			//
			Stuff::OBB parent_bounds;
			parent_bounds.Multiply(child_obb, m_localToParent);
			info->m_bounds->Union(*info->m_bounds, parent_bounds);
		}
	}

	Check_Object(info->m_bounds);

	//
	//----------------------------------------------------------------------
	// Set the new time, then if we have run the course of the effect, start
	// over if we loop, otherwise wait for our children to finish before
	// killing ourselves
	//----------------------------------------------------------------------
	//
	m_lastRan = info->m_time;
	m_age = age;
	if (m_age >= 1.0f)
	{
		m_age = 1.0f;
		if (IsLooped())
			Start(info);
		else if (HasFinished())
			Kill();
	}

	//
	//----------------------------------
	// Tell our parent if we need to die
	//----------------------------------
	//
	gos_PopCurrentHeap();
	return IsExecuted();
}

//------------------------------------------------------------------------------
//
void gosFX::Effect::Stop()
{
	Check_Object(this);

	//
	//-----------------
	// Stop this effect
	//-----------------
	//
	m_ageRate = 1.0e6f;
	SetLoopOff();
	if (m_event.GetCurrent() != NULL)
	{
		m_event.Last();
		m_event.Next();
	}

	//
	//---------------------------
	// Now stop all child effects
	//---------------------------
	//
	Stuff::ChainIteratorOf<gosFX::Effect*> children(&m_children);
	gosFX::Effect *child;
	while ((child = children.ReadAndNext()) != NULL)
	{
		Check_Object(child);
		child->Stop();
	}
}

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

	//
	//------------------------------------
	// Kill the children then kill ourself
	//------------------------------------
	//
	Stuff::ChainIteratorOf<gosFX::Effect*> children(&m_children);
	gosFX::Effect* child;
	while ((child = children.ReadAndNext()) != NULL)
	{
		Check_Object(child);
		child->Kill();
	}
	SetExecuteOff();
}

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

	//
	//-------------------------------------
	// Make sure all the children get drawn
	//-------------------------------------
	//
	DrawInfo new_info;
	Check_Object(m_specification);
	new_info.m_state.Combine(info->m_state, m_specification->m_state);
	Stuff::LinearMatrix4D local_to_world;
	local_to_world.Multiply(m_localToParent, *info->m_parentToWorld);
	new_info.m_parentToWorld = &local_to_world;
	new_info.m_clipper = info->m_clipper;
	new_info.m_clippingFlags = info->m_clippingFlags;
	Stuff::ChainIteratorOf<gosFX::Effect*> children(&m_children);
	gosFX::Effect *child;
	while ((child = children.ReadAndNext()) != NULL)
	{
		Check_Object(child);
		child->Draw(&new_info);
	}
}

//------------------------------------------------------------------------------
//
bool gosFX::Effect::HasFinished()
{
	Check_Object(this);

	//
	//-------------------------------------------------------------------------
	// An effect is not finished if it is executing and its life hasn't expired
	//-------------------------------------------------------------------------
	//
	if (IsExecuted() && m_age < 1.0f)
		return false;

	//
	//-----------------------------------------------
	// It is also not finished if it has any children
	//-----------------------------------------------
	//
	Stuff::ChainIteratorOf<gosFX::Effect*> children(&m_children);
	return children.GetCurrent() == NULL;
}

//#############################################################################
//##########################    gosFX::Effect__ClassData    ##########################
//#############################################################################

void
	gosFX::Effect__ClassData::TestInstance()
{
	Verify(IsDerivedFrom(gosFX::Effect::DefaultData));
}
