/*
**	Command & Conquer Renegade(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	This program is free software: you can redistribute it and/or modify
**	it under the terms of the GNU General Public License as published by
**	the Free Software Foundation, either version 3 of the License, or
**	(at your option) any later version.
**
**	This program is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**	GNU General Public License for more details.
**
**	You should have received a copy of the GNU General Public License
**	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/***********************************************************************************************
 ***              C O N F I D E N T I A L  ---  W E S T W O O D  S T U D I O S               ***
 ***********************************************************************************************
 *                                                                                             *
 *                 Project Name : Commando                                                     *
 *                                                                                             *
 *                     $Archive:: /Commando/Code/Combat/WeatherMgr.cpp        $*
 *                                                                                             *
 *                       Author:: Ian Leslie		                                               *
 *                                                                                             *
 *                     $Modtime:: 1/02/02 1:26p                                     $*
 *                                                                                             *
 *                    $Revision:: 27                                                        $*
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 * Functions:                                                                                  *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

// Includes.
#include "weathermgr.h"
#include "apppackettypes.h"
#include "assetmgr.h"
#include "audiblesound.h"
#include "camera.h"
#include "chunkio.h"
#include "combat.h"
#include "gameobjmanager.h"
#include "gametype.h"
#include "light.h"
#include "dx8indexbuffer.h"
#include "dx8wrapper.h"
#include "phys.h"
#include "physcoltest.h"
#include "pscene.h"
#include "rinfo.h"
#include "scene.h"
#include "sortingrenderer.h"
#include "soundenvironment.h"
#include "wwaudio.h"
#include "wwmemlog.h"


// Singletons.
WeatherMgrClass _TheWeatherMgr;


// Static data.
DEFINE_AUTO_POOL(WeatherSystemClass::RayStruct, WeatherSystemClass::GROWTH_STEP);
DEFINE_AUTO_POOL(WeatherSystemClass::ParticleStruct, WeatherSystemClass::GROWTH_STEP);

Random2Class									 WeatherSystemClass::_RandomNumber (0x60486223);
unsigned											 WeatherSystemClass::_GlobalParticleCount = 0;

SoundEnvironmentClass						*WeatherMgrClass::_SoundEnvironment;
WeatherParameterClass						 WeatherMgrClass::_Parameters [PARAMETER_COUNT];
bool												 WeatherMgrClass::_Prime;
bool												 WeatherMgrClass::_Imported;
unsigned											 WeatherMgrClass::_WindOverrideCount;
unsigned											 WeatherMgrClass::_PrecipitationOverrideCount;

WindClass										*WeatherMgrClass::_Wind;
WeatherSystemClass							*WeatherMgrClass::_Precipitation [PRECIPITATION_COUNT];
bool												 WeatherMgrClass::_FogEnabled;
bool												 WeatherMgrClass::_Dirty;


/***********************************************************************************************
 * WindClass::WindClass --																							  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   04/09/01    IML : Created.                                                                *
 *=============================================================================================*/
WindClass::WindClass (float heading, float speed, float variability, SoundEnvironmentClass *soundenvironment)
	: Velocity (0.0f, 0.0f),
	  SoundEnvironment (soundenvironment)
{
	const char *windsamplename	= "Wind01";

	Set (heading, speed, variability);

	for (unsigned octave = 0; octave < OCTAVE_COUNT; octave++) {
		Theta [octave] = 0.0l;
	}

	// Create the wind sound effect.
	Sound = WWAudioClass::Get_Instance()->Create_Sound (windsamplename, NULL, 0, CLASSID_2D);
	if (Sound != NULL) {
		SoundEnvironment->Add_User();
		Sound->Set_Volume (0.0f);
		Sound->Play();
	}
}


/***********************************************************************************************
 * WindClass::~WindClass --																						  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   04/09/01    IML : Created.                                                                *
 *=============================================================================================*/
WindClass::~WindClass()
{
	// Remove wind sound effect.
	if (Sound != NULL) {
		Sound->Stop();
		REF_PTR_RELEASE (Sound);
		SoundEnvironment->Remove_User();
	}
}


/***********************************************************************************************
 * WindClass::Set --																									  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   04/09/01    IML : Created.                                                                *
 *=============================================================================================*/
void WindClass::Set (float heading, float speed, float variability)
{
	WWASSERT (speed >= 0.0f);

	Heading		= DEG_TO_RADF (heading);
	Speed			= speed;
	Variability = variability;
}


/***********************************************************************************************
 * WindClass::Update --																								  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   04/09/01    IML : Created.                                                                *
 *=============================================================================================*/
bool WindClass::Update()
{
	const double frequency [OCTAVE_COUNT] = {0.5l, 0.2l};
	const double twopi						  = WWMATH_PI * 2.0l;

	float	h, speed;

	if (Variability > 0.0f) {

		float f = 0.0f;
		int	d;

		for (unsigned octave = 0; octave < OCTAVE_COUNT; octave++) {

			Theta [octave] += WW3D::Get_Frame_Time() * 0.001l * frequency [octave];
			d = floorf (Theta [octave] / twopi);
			if (d >= 1) Theta [octave] -= d * twopi;
			f += sinf (Theta [octave]);
		}
		speed = Speed - (Speed * ((f + 1.0f) * 0.5f) * Variability);
	} else {
		speed = Speed;
	}

	h = Heading + (0.5f * WWMATH_PI);
	Velocity.Set (cosf (h) * speed, sinf (h) * speed);

	// Update wind sound effect.
	if (Sound != NULL) {

		const float maxvolumespeed	= 10.0f;

		float attenuation;

		// Precalculate volume attenuation based on speed.
		attenuation = (0.5f * (MIN (Speed, maxvolumespeed) + MIN (speed, maxvolumespeed))) / maxvolumespeed;
		Sound->Set_Volume (SoundEnvironment->Get_Amplitude() * attenuation);
	}

	// Is the wind essentially idle (ie. it is not contributing to the scene visually or audibly)?
	return (Speed > 0.0f);
}


/***********************************************************************************************
 * WeatherSystemClass::WeatherSystemClass --																	  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
WeatherSystemClass::WeatherSystemClass	(PhysicsSceneClass *scene,
													 float emittersize,
													 float emitterheight,
													 float particledensity,
													 float particlesperunitlength,
													 float particlewidth,
													 float particleheight,
													 float particlespeed,
													 const Vector2 &pageoffset,
													 const Vector2 &pagesize,
													 unsigned pagecount,
													 bool	staticpageexists,
													 float minstatictime,
													 float maxstatictime,
													 RenderModeEnum rendermode,
													 bool decayaftercollision,
													 bool prime)
	: Scene (scene),
	  EmitterSize (emittersize),
	  EmitterHeight (emitterheight),
	  EmitterPosition (Vector3 (0.0f, 0.0f, 0.0f)),
	  ParticlesPerUnitLength (particlesperunitlength),
	  ParticleSpeed (particlespeed),
	  HalfParticleWidth (particlewidth * 0.5f),
	  HalfParticleHeight (particleheight * 0.5f),
	  RayHead (NULL),
	  RayCount (0),
	  RaySpawnPtr (NULL),
	  RayUpdatePtr (NULL),
	  ParticleHead (NULL),
	  ParticleCount (0),
	  MinRayEndZ (FLT_MAX),
	  SpawnCountFraction (0.0f),
	  PageCount (pagecount),
	  StaticPageExists (staticpageexists),
	  MinStaticTime (minstatictime),
	  MaxStaticTime (maxstatictime),
	  RenderMode (rendermode),
	  DecayAfterCollision (decayaftercollision),
	  CameraPositionValid (false)
{
	const char								*texturename = "WeatherParticles.tga";
	const TextureClass::MipCountType  mipcount	 = TextureClass::MIP_LEVELS_5;
	const float								 oopagecount = 1.0f / pagecount;

	WWASSERT (particlespeed >= 0.0f);

	// How old is the weather system?
	Age = prime ? MAX_AGE : 0.0f;

	// Set density of system.
	Set_Density (particledensity);

	// Get the scene's bounding box.
	scene->Get_Level_Extents (SceneMin, SceneMax);

	// Expand the bounding box by a small amount so that we can distinguish between collisions with geometry and
	// collisions with the bounding box.
	SceneMin.Z -= 1.0f;
	SceneMax.Z += 1.0f;

	// Initialize an index buffer.
	#if WEATHER_PARTICLE_SORT
	IndexBuffer = NEW_REF (SortingIndexBufferClass, (MAX_IB_PARTICLE_COUNT * VERTICES_PER_TRIANGLE));
	#else
	IndexBuffer = NEW_REF (DX8IndexBufferClass, (MAX_IB_PARTICLE_COUNT * VERTICES_PER_TRIANGLE));
	#endif
	{
		SortingIndexBufferClass::WriteLockClass lock (IndexBuffer);
		unsigned short *indices = lock.Get_Index_Array();

		for (unsigned i = 0; i < MAX_IB_PARTICLE_COUNT * VERTICES_PER_TRIANGLE; i++) {
			*indices++ = i;
		}
	}

	// Configure material.
	Material = VertexMaterialClass::Get_Preset (VertexMaterialClass::PRELIT_NODIFFUSE);

	// Configure shader.
	Shader = ShaderClass::_PresetAlphaShader;
	Shader.Set_Primary_Gradient (ShaderClass::GRADIENT_MODULATE);
	Shader.Set_Cull_Mode (ShaderClass::CULL_MODE_DISABLE);
	Shader.Enable_Fog ("WeatherSystemClass");

	// Configure texture.
	Texture = WW3DAssetManager::Get_Instance()->Get_Texture (texturename, mipcount);
	Texture->Set_U_Addr_Mode (TextureClass::TEXTURE_ADDRESS_CLAMP);
	Texture->Set_V_Addr_Mode (TextureClass::TEXTURE_ADDRESS_CLAMP);
	Set_Translucent (true);

	// Configure texture coordinates according to given page count.
	// NOTE: Split the texture into vertical pages.
	WWASSERT (pagecount > 0);
	TextureArray = new Vector2 [pagecount * VERTICES_PER_TRIANGLE];
	WWASSERT (TextureArray != NULL);
	for (unsigned page = 0; page < pagecount; page++) {
		TextureArray [page * VERTICES_PER_TRIANGLE + 0].Set (pageoffset.U + (((page + 0.5f) * oopagecount) * pagesize.U), pageoffset.V + 0.0f);
		TextureArray [page * VERTICES_PER_TRIANGLE + 1].Set (pageoffset.U + (((page + 1.0f) * oopagecount) * pagesize.U), pageoffset.V + pagesize.V);
		TextureArray [page * VERTICES_PER_TRIANGLE + 2].Set (pageoffset.U + (((page + 0.0f) * oopagecount) * pagesize.U), pageoffset.V + pagesize.V);
	}
}


/***********************************************************************************************
 * WeatherSystemClass::WeatherSystemClass --																	  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
WeatherSystemClass::~WeatherSystemClass()
{
	RayStruct		*rayptr;
	ParticleStruct *particleptr;

	// Clean-up rays.
	rayptr = RayHead;
	while (rayptr != NULL) {

		RayStruct *nextrayptr;

		nextrayptr = rayptr->Next;
		delete rayptr;
		rayptr = nextrayptr;
		RayCount--;
	}

	WWASSERT (RayCount == 0);

	// Clean-up particles.
	particleptr = ParticleHead;
	while (particleptr != NULL) {

		ParticleStruct *nextparticleptr = particleptr->Next;

		Kill (particleptr);
		particleptr = nextparticleptr;
	}

	WWASSERT (ParticleCount == 0);

	REF_PTR_RELEASE (Material);
	delete [] TextureArray;
	REF_PTR_RELEASE (Texture);
	REF_PTR_RELEASE (IndexBuffer);
}


/***********************************************************************************************
 * WeatherSystemClass::Set_Density --																			  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
void WeatherSystemClass::Set_Density (float density)
{
	unsigned   raycount;
	int		  signedcount, r;
	RayStruct *rayptr;

	// Calculate no. of rays required.
	ParticleDensity = density;
	raycount			 = Spawn_Count (1.0f) / (ParticlesPerUnitLength * ParticleSpeed);

	// Is the ray count increasing or decreasing in size?
	signedcount = ((int) RayCount) - ((int) raycount);
	if (signedcount < 0) {

		// Add new, uninitialized rays to head of list.
		for (r = signedcount; r < 0; r++) {

			rayptr = new RayStruct;
			WWASSERT (rayptr != NULL);

			rayptr->Next = RayHead;
			rayptr->Initialized = false;
			RayHead = rayptr;
		}

		// If necessary, initialize the spawn and update pointers.
		if (RaySpawnPtr  == NULL) RaySpawnPtr  = RayHead;
		if (RayUpdatePtr == NULL) RayUpdatePtr = RayHead;

	} else {

		// Remove rays from head of list.
		rayptr = RayHead;
		for (r = 0; r < signedcount; r++) {

			RayStruct *nextrayptr;

			if (rayptr == NULL) break;

			nextrayptr = rayptr->Next;

			// If this ray is referenced by the spawn or update pointers then change them.
			if (RaySpawnPtr  == rayptr) RaySpawnPtr  = nextrayptr;
			if (RayUpdatePtr == rayptr) RayUpdatePtr = nextrayptr;

			delete rayptr;
			rayptr = nextrayptr;
		}
		RayHead = rayptr;
	}

	RayCount = raycount;
}


/***********************************************************************************************
 * WeatherSystemClass::Update --																					  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
bool WeatherSystemClass::Update (WindClass *wind, const Vector3 &cameraposition)
{
	struct BoundingBoxStruct {
		Vector2 Min, Max;
	};

	const unsigned randomness		= 10000;
	const float		oorandomness	= 1.0f / randomness;
	const float		overlapdelta	= EmitterSize * 2.0f;
	const unsigned rayupdatecount = MAX (RayCount * 0.018f, 1);

	Vector3				oldemitterposition;
	float					ooz;
	Vector3				emitterdirection, emitteroffset;
	Vector3				projectedemitterposition;
	BoundingBoxStruct emitterbounds;
	float					deltax, deltay;
	BoundingBoxStruct nonoverlapregions [2];
	float					regionthreshold;
	Vector3				sceneminoffset, scenemaxoffset;
	Vector2				raystartoffset;
	RayStruct		  *rayptr;
	float					spawncountfraction;
	float					alpha, beta;
	Vector2				range;
	Vector3				minrayendposition;
	float					l, boxoffset;
	ParticleStruct	  *particleptr;
	float					s;
	unsigned				spawncount;
	float					time;

	oldemitterposition = EmitterPosition;

	// Calculate particle velocity based on wind.
	if (wind != NULL) {
		ParticleVelocity.Set (wind->Get_Velocity().X, wind->Get_Velocity().Y, -ParticleSpeed);
	} else {
		ParticleVelocity.Set (0.0f, 0.0f, -ParticleSpeed);
	}

	// Calculate new emitter position.
	ooz = 1.0f / ParticleVelocity.Z;
	emitterdirection.Set (ParticleVelocity.X * ooz, ParticleVelocity.Y * ooz, 1.0f);
	emitteroffset = EmitterHeight * emitterdirection;
	EmitterPosition =	cameraposition + emitteroffset;

	// Define bounding box for new emitter area.
	emitterbounds.Min.Set (EmitterPosition.X - EmitterSize, EmitterPosition.Y - EmitterSize);
	emitterbounds.Max.Set (EmitterPosition.X + EmitterSize, EmitterPosition.Y + EmitterSize);

	// Calculate new regions that define the areas of non-overlap between the old and new emitter positions.

	// Project the old emitter onto the plane of the new emitter.
	projectedemitterposition = oldemitterposition + ((EmitterPosition.Z - oldemitterposition.Z) * emitterdirection);

	// If the old and new emitters partially overlap...
	deltax = WWMath::Fabs (EmitterPosition.X - projectedemitterposition.X);
	deltay = WWMath::Fabs (EmitterPosition.Y - projectedemitterposition.Y);
	if ((deltax < overlapdelta) && (deltay < overlapdelta) && (EmitterPosition != projectedemitterposition)) {

		float area0, area1;

		// Compare X-extents.
		if (EmitterPosition.X < projectedemitterposition.X) {
			nonoverlapregions [0].Min.X = emitterbounds.Min.X;
			nonoverlapregions [0].Max.X = projectedemitterposition.X - EmitterSize;
			nonoverlapregions [1].Min.X = nonoverlapregions [0].Max.X;
			nonoverlapregions [1].Max.X = emitterbounds.Max.X;
		} else {
			nonoverlapregions [0].Min.X = projectedemitterposition.X + EmitterSize;
			nonoverlapregions [0].Max.X = emitterbounds.Max.X;
			nonoverlapregions [1].Min.X = emitterbounds.Min.X;
			nonoverlapregions [1].Max.X = nonoverlapregions [0].Min.X;
		}
		nonoverlapregions [0].Min.Y = emitterbounds.Min.Y;
		nonoverlapregions [0].Max.Y = emitterbounds.Max.Y;

		// Compare Y-extents.
		if (EmitterPosition.Y < projectedemitterposition.Y) {
			nonoverlapregions [1].Min.Y = emitterbounds.Min.Y;
			nonoverlapregions [1].Max.Y = projectedemitterposition.Y - EmitterSize;
		} else {
			nonoverlapregions [1].Min.Y = projectedemitterposition.Y + EmitterSize;
			nonoverlapregions [1].Max.Y = emitterbounds.Max.Y;
		}

		area0 = (nonoverlapregions [0].Max.X - nonoverlapregions [0].Min.X) * (nonoverlapregions [0].Max.Y - nonoverlapregions [0].Min.Y);
		area1 = (nonoverlapregions [1].Max.X - nonoverlapregions [1].Min.X) * (nonoverlapregions [1].Max.Y - nonoverlapregions [1].Min.Y);
		if (area0 == 0.0f) {

			// Always select region 1.
			regionthreshold = -FLT_MAX;

		} else {
			if (area1 == 0.0f) {

				// Always select region 0.
				regionthreshold = +FLT_MAX;

			} else {

				// Select either region 0 or region 1 weighted by area.
				regionthreshold = area0 / (area0 + area1);
			}
		}

	} else {

		// No overlap or complete overlap: region 0 is emitter area, region 1 is not used.
		nonoverlapregions [0] = emitterbounds;
		regionthreshold = +FLT_MAX;
	}

	// Calculate offset of projection of ray start from old to new emitter.
	raystartoffset	= (EmitterPosition.Z - oldemitterposition.Z) * Vector2 (emitterdirection.X, emitterdirection.Y);

	// Precalculate offsets to intersection with upper and lower planes of scene bounds from emitter position.
	sceneminoffset = (SceneMin.Z - EmitterPosition.Z) * emitterdirection;
	scenemaxoffset = (SceneMax.Z - EmitterPosition.Z) * emitterdirection;

	// Iterate over all rays...
	spawncountfraction = 0.0f;
	rayptr = RayHead;
	while (rayptr != NULL) {

	  	Vector3 raystartposition;

		// Does this ray need to be initialized?
		if (!rayptr->Initialized) {

			// Randomly allocate a new ray start position from the entire emitter region.
			alpha = _RandomNumber (0, randomness) * oorandomness;
			beta  = _RandomNumber (0, randomness) * oorandomness;
			range = emitterbounds.Max - emitterbounds.Min;
			range.Scale (alpha, beta);
			rayptr->StartPosition = emitterbounds.Min + range;
			rayptr->Initialized	 = true;
			rayptr->RayCast		 = true;

		} else {

		  	// Project the ray's emitter position onto the plane of the new emitter.
			rayptr->StartPosition += raystartoffset;

			// Does the ray fall outside the emitter (and therefore needs to be raycast)?
			rayptr->RayCast = (rayptr->StartPosition.X < emitterbounds.Min.X) ||
									(rayptr->StartPosition.X > emitterbounds.Max.X) ||
									(rayptr->StartPosition.Y < emitterbounds.Min.Y) ||
									(rayptr->StartPosition.Y > emitterbounds.Max.Y);

			if (rayptr->RayCast) {

				unsigned regionindex;

				// Randomly allocate a new ray start position from one of the non-overlap regions.
				if ((_RandomNumber (0, randomness) * oorandomness) < regionthreshold) {
					regionindex = 0;
				} else {
					regionindex = 1;
				}
				alpha = _RandomNumber (0, randomness) * oorandomness;
				beta  = _RandomNumber (0, randomness) * oorandomness;
				range = nonoverlapregions [regionindex].Max - nonoverlapregions [regionindex].Min;
				range.Scale (alpha, beta);
				rayptr->StartPosition = nonoverlapregions [regionindex].Min + range;

			} else {

				// Next ray.
				rayptr = rayptr->Next;
				continue;
			}
		}

		raystartposition.Set (rayptr->StartPosition.X, rayptr->StartPosition.Y, EmitterPosition.Z);

		// Raycast to find the ray's collision point with the environment.
		{
			Vector3						  raycaststartpoint (raystartposition + scenemaxoffset);
			Vector3						  raycastendpoint (raystartposition + sceneminoffset);
			LineSegClass				  raycast (raycaststartpoint, raycastendpoint);
			CastResultStruct			  rayresult;
			PhysRayCollisionTestClass raytest (raycast, &rayresult, TERRAIN_ONLY_COLLISION_GROUP, COLLISION_TYPE_PROJECTILE);

			Scene->Cast_Ray (raytest);
			raycast.Compute_Point (raytest.Result->Fraction, &(rayptr->EndPosition));
			if (raytest.Result->Fraction < 1.0f) {
				rayptr->ValidSurfaceNormal = true;
				rayptr->SurfaceNormal = raytest.Result->Normal;
			} else {
				rayptr->ValidSurfaceNormal = false;
			}
		}

		rayptr->ParticleVelocity = ParticleVelocity;

		if ((Age > 0.0f) && (Can_Spawn (rayptr))) {

			float		s;
			unsigned	spawncount;

			// Spawn some particles along the ray.
			// NOTE: For accuracy, accumulate fractional spawncounts so that they can be used on a later ray.
			s = ParticlesPerUnitLength * (rayptr->EndPosition - raystartposition).Quick_Length();
			spawncount = floor (s);
			spawncountfraction += s - spawncount;
			if (spawncountfraction >= 1.0f) {
				spawncountfraction -= 1.0f;
				spawncount++;
			}
			for (unsigned p = 0; p < spawncount; p++) {
				Spawn (rayptr);
			}
		}

		// Update minimum ray end Z.
		if (rayptr->EndPosition.Z < MinRayEndZ) {
			MinRayEndZ = rayptr->EndPosition.Z;
		}

		// Next ray.
		rayptr = rayptr->Next;
	}

	// Now iterate over rayupdatecount rays and randomize them so that the ray 'pattern' is
	// not discernable to the user (because it is constantly changing) and also to take
	// account of the new particle velocity.
	if (RayUpdatePtr != NULL) {

		for (unsigned r = 0; r < rayupdatecount; r++) {

			// NOTE: Only need to randomize those rays that have not just been relocated inside the emitter.
			if (!RayUpdatePtr->RayCast) {

				// Randomly allocate a new ray start position from the entire emitter region.
				alpha = _RandomNumber (0, randomness) * oorandomness;
				beta  = _RandomNumber (0, randomness) * oorandomness;
				range = emitterbounds.Max - emitterbounds.Min;
				range.Scale (alpha, beta);
				RayUpdatePtr->StartPosition = emitterbounds.Min + range;

				// Raycast to find the ray's collision point with the environment.
				{
					Vector3						  raystartposition (RayUpdatePtr->StartPosition.X, RayUpdatePtr->StartPosition.Y, EmitterPosition.Z);
					Vector3						  raycaststartpoint (raystartposition + scenemaxoffset);
					Vector3						  raycastendpoint (raystartposition + sceneminoffset);
					LineSegClass				  raycast (raycaststartpoint, raycastendpoint);
					CastResultStruct			  rayresult;
					PhysRayCollisionTestClass raytest (raycast, &rayresult, TERRAIN_ONLY_COLLISION_GROUP, COLLISION_TYPE_PROJECTILE);

					Scene->Cast_Ray (raytest);
					raycast.Compute_Point (raytest.Result->Fraction, &(RayUpdatePtr->EndPosition));
					if (raytest.Result->Fraction < 1.0f) {
						RayUpdatePtr->ValidSurfaceNormal = true;
						RayUpdatePtr->SurfaceNormal = raytest.Result->Normal;
					} else {
						RayUpdatePtr->ValidSurfaceNormal = false;
					}
				}

				RayUpdatePtr->ParticleVelocity = ParticleVelocity;

				// Update minimum ray end Z.
				if (RayUpdatePtr->EndPosition.Z < MinRayEndZ) {
					MinRayEndZ = RayUpdatePtr->EndPosition.Z;
				}
			}

			// Next ray. If necessary, wrap around to head of list.
			RayUpdatePtr = RayUpdatePtr->Next;
			if (RayUpdatePtr == NULL) RayUpdatePtr = RayHead;
		}
	}

	// Calculate a bounding box for the render object that encompasses the rays.
	// NOTE 0: For efficiency, calculate the parallelepiped that is generated by projecting
	//			  the emitter area onto the plane that contains the lowest ray end point and put
	//			  a bounding box around this.
	// NOTE 1: Make the bounding box a little bigger to account for the particle point size.
	minrayendposition = EmitterPosition + ((MinRayEndZ - EmitterPosition.Z) * emitterdirection);
	l = MAX (HalfParticleWidth, HalfParticleHeight);
	boxoffset = EmitterSize + l;
	ObjectMax.Set (MAX (EmitterPosition.X, minrayendposition.X) + boxoffset, MAX (EmitterPosition.Y, minrayendposition.Y) + boxoffset, MAX (EmitterPosition.Z, minrayendposition.Z) + l);
	ObjectMin.Set (MIN (EmitterPosition.X, minrayendposition.X) - boxoffset, MIN (EmitterPosition.Y, minrayendposition.Y) - boxoffset, MIN (EmitterPosition.Z, minrayendposition.Z) - l);

	// Flag that the object bounding box has been modified.
	Invalidate_Cached_Bounding_Volumes();

	// Iterate over all particles...
	time = WW3D::Get_Frame_Time() * 0.001f;
	particleptr = ParticleHead;
	while (particleptr != NULL) {

		Vector2 emitterposition;
		bool	  outside;

		ParticleStruct *nextparticleptr = particleptr->Next;

		// Advance time.
		particleptr->ElapsedTime += time;

		// Has it expired?
		if (particleptr->ElapsedTime >= particleptr->LifeTime) {

			Kill (particleptr);
			particleptr = nextparticleptr;
			continue;
		}

		// Has it just collided?
		if (particleptr->ElapsedTime >= particleptr->CollisionTime) {
			if (particleptr->Velocity.Z != 0.0f) {

				// Place the particle at the collision point.
				particleptr->Velocity.Set (0.0f, 0.0f, 0.0f);
				particleptr->CurrentPosition = particleptr->CollisionPosition;
				if (StaticPageExists) particleptr->Page = PageCount - 1;
				particleptr->RenderMode = RENDER_MODE_SURFACE_ALIGNED;
			}

		} else {

			// Advance position.
			particleptr->CurrentPosition += particleptr->Velocity * time;
		}

		// Project the particle's position onto the plane of the new emitter.
		emitterposition = Vector2 (particleptr->CurrentPosition.X, particleptr->CurrentPosition.Y) + ((EmitterPosition.Z - particleptr->CurrentPosition.Z) * particleptr->UnitZVelocity);

		// Does the particle fall outside the emitter?
		outside = (emitterposition.X < emitterbounds.Min.X) ||
					 (emitterposition.X > emitterbounds.Max.X) ||
					 (emitterposition.Y < emitterbounds.Min.Y) ||
					 (emitterposition.Y > emitterbounds.Max.Y);

		if (outside) {
			Kill (particleptr);
		}

		particleptr = nextparticleptr;
	}


	// Spawn any new particles that need to be spawned on this update.
	// NOTE: For accuracy, accumulate fractional spawncounts so that they can be used on a later iteration.
	s = Spawn_Count (time);
	spawncount = floor (s);
	SpawnCountFraction += s - spawncount;
	if (SpawnCountFraction >= 1.0f) {
		SpawnCountFraction -= 1.0f;
		spawncount++;
	}
	for (unsigned p = 0; p < spawncount; p++) {
		Spawn();
	}

	// Advance weather system age.
	// NOTE: To prevent floating point overflow, don't advance the age past some maximum.
	if (Age < MAX_AGE) Age += time;

	// Is the weather still active (ie. it is contributing to the scene visually or audibly)?
	return ((ParticleDensity > 0.0f) || (ParticleCount > 0));
}


/***********************************************************************************************
 * WeatherSystemClass::Spawn --																					  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
bool WeatherSystemClass::Spawn (RayStruct *suppliedrayptr)
{
	const unsigned maxparticlecount = USHRT_MAX / (2 * VERTICES_PER_TRIANGLE);

	// Due to a limitation in the underlying rendering API, there cannot be more than USHRT_MAX
	// total vertices in the system. Conservatively restrict this to half of this to compensate
	// for other primitives in the system.
	if (_GlobalParticleCount < maxparticlecount) {

		RayStruct *rayptr;

		if (suppliedrayptr == NULL) {

			// Assign a ray to this particle.
			// If there are no rays to assign then return failure to spawn.
			if (RaySpawnPtr != NULL) {
				rayptr = RaySpawnPtr;
				RaySpawnPtr = RaySpawnPtr->Next;
				if (RaySpawnPtr == NULL) RaySpawnPtr = RayHead;
			} else {
				return (false);
			}

		} else {
			rayptr = suppliedrayptr;
		}

		// Ensure that this ray's endpoint lies below the emitter (otherwise there is an obstruction
		// between the ray's source and the emitter).
		if (Can_Spawn (rayptr)) {

			const unsigned randomness	  = 1000;
			const float		oorandomness  = 1.0f / randomness;
			const float		ooz			  = 1.0f / rayptr->ParticleVelocity.Z;
			const float		collisiontime = (rayptr->EndPosition.Z - EmitterPosition.Z) * ooz;

			ParticleStruct *particleptr;

			particleptr = new ParticleStruct;
			WWASSERT (particleptr != NULL);

			// Add this particle to the head of the list.
			if (ParticleHead != NULL) {
				ParticleHead->Prev = particleptr;
			}
			particleptr->Prev = NULL;
			particleptr->Next = ParticleHead;
			ParticleHead		= particleptr;

			// Increment the no. of particles.
			ParticleCount++;

			// Increment total no. of global weather particles.
			_GlobalParticleCount++;

			particleptr->UnitZVelocity.Set (rayptr->ParticleVelocity.X * ooz, rayptr->ParticleVelocity.Y * ooz);
			particleptr->CollisionTime		 = collisiontime;
			particleptr->CollisionPosition = rayptr->EndPosition;

			// If the ray does not have a valid surface normal then the ray's end position intersects the level's bounding box.
			// In this case it is not necessary to sustain the particle past the collision time.
			if (rayptr->ValidSurfaceNormal) {
				particleptr->LifeTime = collisiontime + WWMath::Lerp (MinStaticTime, MaxStaticTime, _RandomNumber (0, randomness) * oorandomness);
				particleptr->SurfaceNormal = rayptr->SurfaceNormal;
			} else {
				particleptr->LifeTime = collisiontime;
			}

			if (suppliedrayptr == NULL) {

				// Start particle at emitter.
				particleptr->ElapsedTime	  = 0.0f;
				particleptr->Velocity		  = rayptr->ParticleVelocity;
				particleptr->CurrentPosition = Vector3 (rayptr->StartPosition.X, rayptr->StartPosition.Y, EmitterPosition.Z);
				particleptr->Page				  = _RandomNumber (0, PageCount - (StaticPageExists) ? 2 : 1);
				particleptr->RenderMode		  = RenderMode;

			} else {

				float t;

				// Advance the particle some random amount in time.
				// NOTE: The particle cannot have existed longer than the weather system itself.
				t = _RandomNumber (0, randomness) * oorandomness * particleptr->LifeTime;
				particleptr->ElapsedTime = MIN (t, Age);
				if (particleptr->ElapsedTime >= particleptr->CollisionTime) {
					particleptr->Velocity.Set (0.0f, 0.0f, 0.0f);
					particleptr->CurrentPosition = rayptr->EndPosition;
					if (StaticPageExists) {
						particleptr->Page	= PageCount - 1;
					} else {
						particleptr->Page	= _RandomNumber (0, PageCount - 1);
					}
					particleptr->RenderMode = RENDER_MODE_SURFACE_ALIGNED;

				} else {
					particleptr->Velocity		  = rayptr->ParticleVelocity;
					particleptr->CurrentPosition = Vector3 (rayptr->StartPosition.X, rayptr->StartPosition.Y, EmitterPosition.Z) + (particleptr->Velocity * particleptr->ElapsedTime);
					particleptr->Page				  = _RandomNumber (0, PageCount - (StaticPageExists) ? 2 : 1);
					particleptr->RenderMode		  = RenderMode;
				}
			}

			return (true);
		}
	}

	return (false);
}


/***********************************************************************************************
 * WeatherSystemClass::Kill --																					  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
void WeatherSystemClass::Kill (ParticleStruct *particleptr)
{
	WWASSERT (ParticleCount > 0);

	// Remove this particle from the list.
	if (particleptr->Prev != NULL) {
		particleptr->Prev->Next = particleptr->Next;
	} else {
		ParticleHead = particleptr->Next;
	}
	if (particleptr->Next != NULL) {
		particleptr->Next->Prev = particleptr->Prev;
	}

	delete particleptr;

	// Decrement the no. of particles.
	ParticleCount--;

	// Decrement the no. of global weather particles.
	_GlobalParticleCount--;
}


/***********************************************************************************************
 * WeatherSystemClass::Render --																					  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
void WeatherSystemClass::Render (RenderInfoClass &rinfo)
{
	if (WW3D::Are_Static_Sort_Lists_Enabled()) {

		const unsigned sortlevel = 31;

		WW3D::Add_To_Static_Sort_List (this, sortlevel);

	} else {

		const Vector3 zero  (0.0f, 0.0f, 0.0f);
		const Vector3 white (1.0f, 1.0f, 1.0f);

		Vector3			 cameravelocity;
		Vector3			 color;
		unsigned			 dxcolor;
		Matrix4			 viewmatrix (true), identitymatrix (true);
		float				 maxalphaheight, oomaxalphaheight, deltaheight;
		Vector3			 x, y;
		float				 w, h;
		Vector3			 offset [VERTICES_PER_TRIANGLE];
		Vector3			 camerafocus;
		ParticleStruct *particleptr;
		unsigned			 processedparticlecount, bufferparticlecount;

		if (CameraPositionValid) {
			cameravelocity = ((rinfo.Camera.Get_Position() - CameraPosition) / (WW3D::Get_Frame_Time() * 0.001f));
		} else {
			cameravelocity.Set (0.0f, 0.0f, 0.0f);
			CameraPositionValid = true;
		}

		CameraPosition = rinfo.Camera.Get_Position();

		dxcolor = DX8Wrapper::Convert_Color (Vector3 (1.0f, 1.0f, 1.0f), 0.0f);

		// Precalculate alpha values.
		maxalphaheight	  = EmitterHeight * 0.2f;
		oomaxalphaheight = 1.0f / maxalphaheight;
		deltaheight		  = rinfo.Camera.Get_Position().Z + (EmitterHeight - maxalphaheight);

		// NOTE: All particle positions are already in world space.
		DX8Wrapper::Set_Transform (D3DTS_WORLD, identitymatrix);

		DX8Wrapper::Set_Material (Material);
		DX8Wrapper::Set_Shader (Shader);
		DX8Wrapper::Set_Texture (0, Texture);

		DX8Wrapper::Set_Index_Buffer (IndexBuffer, 0);

		#if WEATHER_PARTICLE_SORT
		#else
		DX8Wrapper::Set_DX8_Render_State (D3DRS_ZBIAS, 12);
		#endif

 		camerafocus = rinfo.Camera.Get_Transform().Get_Z_Vector();
		particleptr = ParticleHead;
		processedparticlecount = 0;
		bufferparticlecount = MIN (MAX_IB_PARTICLE_COUNT, ParticleCount);
		while (processedparticlecount < ParticleCount) {

			unsigned particlecount, submittedparticlecount;

			#if WEATHER_PARTICLE_SORT
			DynamicVBAccessClass dynamicvb (BUFFER_TYPE_DYNAMIC_SORTING, dynamic_fvf_type, bufferparticlecount * VERTICES_PER_TRIANGLE);
			#else
			DynamicVBAccessClass dynamicvb (BUFFER_TYPE_DYNAMIC_DX8, dynamic_fvf_type, bufferparticlecount * VERTICES_PER_TRIANGLE);
			#endif

			// Copy the data into the sorting vertex buffer.
			particlecount = MIN (ParticleCount - processedparticlecount, MAX_IB_PARTICLE_COUNT);
			submittedparticlecount = 0;
			{
				DynamicVBAccessClass::WriteLockClass lock (&dynamicvb);

				VertexFormatXYZNDUV2 *vertex = lock.Get_Formatted_Vertex_Array();

				for (unsigned p = 0; p < particlecount; p++)	{

					Vector3	position;

					WWASSERT (particleptr != NULL);
					position = particleptr->CurrentPosition;

					// Optimization: only submit this particle for rendering if it is in the view frustum.
					if (CollisionMath::Overlap_Test (rinfo.Camera.Get_Frustum(), position) != CollisionMath::OUTSIDE) {

						unsigned base;
						float		alphaheight, alpha;

						switch (particleptr->RenderMode) {

							case RENDER_MODE_AXIS_ALIGNED:
								y = particleptr->Velocity - cameravelocity;
								y /= y.Quick_Length();
								x = Vector3::Cross_Product (camerafocus, y);
								x /= x.Quick_Length();
								w = HalfParticleWidth;
								h = HalfParticleHeight;
								break;

							case RENDER_MODE_CAMERA_ALIGNED:
								x = rinfo.Camera.Get_Transform().Get_X_Vector();
								y = rinfo.Camera.Get_Transform().Get_Y_Vector();
								w = HalfParticleWidth;
								h = HalfParticleHeight;
								break;

							case RENDER_MODE_SURFACE_ALIGNED:

								// Particle has an orientation so back-face cull it.
								if (Vector3::Dot_Product (camerafocus, particleptr->SurfaceNormal) > 0.0f) {

									x = Vector3::Cross_Product (camerafocus, particleptr->SurfaceNormal);
									x /= x.Quick_Length();
									y = Vector3::Cross_Product (x, particleptr->SurfaceNormal);

									if (DecayAfterCollision) {

										float decaytime, totaldecaytime, s;

										decaytime		= particleptr->ElapsedTime - particleptr->CollisionTime;
										totaldecaytime = particleptr->LifeTime - particleptr->CollisionTime;
										if ((decaytime >= 0.0f) && (totaldecaytime > 0.0f)) {
											s = 1.0f - (decaytime / totaldecaytime);
											w = HalfParticleWidth  * s;
											h = HalfParticleHeight * s;
										} else {
											w = h = 0.0f;
										}

									} else {
										w = HalfParticleWidth;
										h = HalfParticleHeight;
									}
									break;

								} else {

				  					// Advance to next particle.
									particleptr = particleptr->Next;

									continue;
								}

 							default:
								WWASSERT (false);
								w = h = 0.0f;
								break;
						}

						offset [0] = -h * y;
						offset [1] =  h * y + w * x;
						offset [2] =  h * y - w * x;

						base = particleptr->Page * VERTICES_PER_TRIANGLE;

						alphaheight = position.Z - deltaheight;
						if (alphaheight > 0.0f) {
							alpha = 1.0f - (alphaheight * oomaxalphaheight);
						} else {
							alpha = 1.0f;
						}
						DX8Wrapper::Set_Alpha (alpha, dxcolor);

						// Vertex 0 of triangle.
						vertex->x		 = position.X + offset [0].X;
						vertex->y		 = position.Y + offset [0].Y;
						vertex->z		 = position.Z + offset [0].Z;
						vertex->diffuse = dxcolor;
						vertex->u1		 = TextureArray [base].U;
						vertex->v1		 = TextureArray [base].V;
						vertex++;

						// Vertex 1 of triangle.
						vertex->x		 = position.X + offset [1].X;
						vertex->y		 = position.Y + offset [1].Y;
						vertex->z		 = position.Z + offset [1].Z;
						vertex->diffuse = dxcolor;
						vertex->u1		 = TextureArray [base + 1].U;
						vertex->v1		 = TextureArray [base + 1].V;
						vertex++;

						// Vertex 2 of triangle.
						vertex->x		 = position.X + offset [2].X;
						vertex->y		 = position.Y + offset [2].Y;
						vertex->z		 = position.Z + offset [2].Z;
						vertex->diffuse = dxcolor;
						vertex->u1		 = TextureArray [base + 2].U;
						vertex->v1		 = TextureArray [base + 2].V;
						vertex++;

						submittedparticlecount++;
					}

					// Advance to next particle.
					particleptr = particleptr->Next;
				}
			}

			if (submittedparticlecount > 0) {

				DX8Wrapper::Set_Vertex_Buffer (dynamicvb);

				#if WEATHER_PARTICLE_SORT
				SortingRendererClass::Insert_Triangles (0, submittedparticlecount, 0, submittedparticlecount * VERTICES_PER_TRIANGLE);
				#else
				DX8Wrapper::Draw_Triangles (0, submittedparticlecount, 0, submittedparticlecount * VERTICES_PER_TRIANGLE);
				#endif
			}

			processedparticlecount += particlecount;
		}

		WWASSERT (particleptr == NULL);

		#if WEATHER_PARTICLE_SORT
		#else
		DX8Wrapper::Set_DX8_Render_State (D3DRS_ZBIAS, 0);
		#endif
	}
}


/***********************************************************************************************
 * WeatherSystemClass::Get_Obj_Space_Bounding_Sphere --													  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
void WeatherSystemClass::Get_Obj_Space_Bounding_Sphere (SphereClass &sphere) const
{
	sphere.Init ((ObjectMin + ObjectMax) * 0.5f, ((ObjectMax - ObjectMin) * 0.5f).Length());
}


/***********************************************************************************************
 * WeatherSystemClass::Get_Obj_Space_Bounding_Box --														  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
void WeatherSystemClass::Get_Obj_Space_Bounding_Box (AABoxClass &box) const
{
	box.Init ((ObjectMin + ObjectMax) * 0.5f, (ObjectMax - ObjectMin) * 0.5f);
}


/***********************************************************************************************
 * RainSystemClass::RainSystemClass --																			  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
RainSystemClass::RainSystemClass (PhysicsSceneClass *scene, float particledensity, WindClass *wind, SoundEnvironmentClass *soundenvironment, bool prime)
	: WeatherSystemClass (scene, 20.0f, 20.0f, particledensity, 0.2f, 0.15f, 0.45f, 15.0f, Vector2 (0.0f, 0.0f), Vector2 (1.0f, 0.5f), PAGE_COUNT, true, 0.1f, 0.2f, WeatherSystemClass::RENDER_MODE_AXIS_ALIGNED, false, prime),
	  SoundEnvironment (soundenvironment)
{
	const char *rainsamplename	= "Rainfall01";

	// Create the rain sound effect.
	// Optimization: Only add the sound effect if wind speed is non-zero.
	Sound = WWAudioClass::Get_Instance()->Create_Sound (rainsamplename, NULL, 0, CLASSID_2D);
	if (Sound != NULL) {
		SoundEnvironment->Add_User();
		Sound->Set_Volume (0.0f);
		Sound->Play();
 	}
}


/***********************************************************************************************
 * RainSystemClass::~RainSystemClass --																		  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
RainSystemClass::~RainSystemClass()
{
	// Remove rain sound effect.
	if (Sound != NULL) {
		Sound->Stop();
		REF_PTR_RELEASE (Sound);
		SoundEnvironment->Remove_User();
	}
}


/***********************************************************************************************
 * RainSystemClass::Update --																						  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
bool RainSystemClass::Update (WindClass *wind, const Vector3 &cameraposition)
{
	if (Sound != NULL) {

		const float maxvolume			= 4.0f;
		const float volumeperparticle	= 0.0025f;

		float attenuation;

		// Calculate sound attenuation based on no. of particles in system.
		attenuation = MIN (ParticleCount * volumeperparticle, maxvolume) / maxvolume;
		Sound->Set_Volume (SoundEnvironment->Get_Amplitude() * attenuation);
	}

	// Base class update.
	return (WeatherSystemClass::Update (wind, cameraposition));
}


/***********************************************************************************************
 * SnowSystemClass::SnowSystemClass --																			  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
SnowSystemClass::SnowSystemClass (PhysicsSceneClass *scene, float particledensity, WindClass *wind, bool prime)
	: WeatherSystemClass (scene, 40.0f, 20.0f, particledensity, 0.1f, 0.32f, 0.32f, 3.5f, Vector2 (0.0f, 0.5f), Vector2 (1.0f, 0.25f), PAGE_COUNT, false, 1.0f, 2.0f, WeatherSystemClass::RENDER_MODE_CAMERA_ALIGNED, true, prime)
{
}


/***********************************************************************************************
 * SnowSystemClass::Update --																						  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
bool SnowSystemClass::Update (WindClass *wind, const Vector3 &cameraposition)
{
	// Base class update.
	return (WeatherSystemClass::Update (wind, cameraposition));
}


/***********************************************************************************************
 * AshSystemClass::AshSystemClass --																			  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
AshSystemClass::AshSystemClass (PhysicsSceneClass *scene, float particledensity, WindClass *wind, bool prime)
	: WeatherSystemClass (scene, 40.0f, 20.0f, particledensity, 0.1f, 0.32f, 0.32f, 3.0f, Vector2 (0.0f, 0.75f), Vector2 (1.0f, 0.25f), PAGE_COUNT, false, 1.0f, 2.0f, WeatherSystemClass::RENDER_MODE_CAMERA_ALIGNED, true, prime)
{
}


/***********************************************************************************************
 * AshSystemClass::Update --																						  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
bool AshSystemClass::Update (WindClass *wind, const Vector3 &cameraposition)
{
	// Base class update.
	return (WeatherSystemClass::Update (wind, cameraposition));
}


/***********************************************************************************************
 * WeatherParameterClass::Initialize --																		  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   04/24/01    IML : Created.                                                                *
 *=============================================================================================*/
void WeatherParameterClass::Initialize()
{
	CurrentValue	  = 0.0f;
	NormalTarget	  = 0.0f;
	NormalDuration	  = 0.0f;
	OverrideTarget	  = 0.0f;
	OverrideDuration = 0.0f;
}


/***********************************************************************************************
 * WeatherParameterClass::Set --																					  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   04/24/01    IML : Created.                                                                *
 *=============================================================================================*/
void WeatherParameterClass::Set (float target, float ramptime, bool override)
{
	if (!override) {
		NormalTarget	  = target;
		NormalDuration	  = ramptime;
	} else {
		OverrideTarget	  = target;
		OverrideDuration = ramptime;
	}
}


/***********************************************************************************************
 * WeatherParameterClass::Update --																				  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   04/24/01    IML : Created.                                                                *
 *=============================================================================================*/
bool WeatherParameterClass::Update (float time, bool override)
{
	const float previouscurrentvalue = CurrentValue;

	// Update the normal parameters.
	Update (NormalValue, NormalTarget, NormalDuration, time);

	// Update the override parameters?
	if (override) {
		Update (CurrentValue, OverrideTarget, OverrideDuration, time);
	} else {
		if (OverrideDuration > 0.0f) {
			Update (CurrentValue, NormalValue, OverrideDuration, time);
		} else {
			CurrentValue = NormalValue;
		}
	}

	return (CurrentValue != previouscurrentvalue);
}


/***********************************************************************************************
 * WeatherParameterClass::Update --																				  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   04/24/01    IML : Created.                                                                *
 *=============================================================================================*/
void WeatherParameterClass::Update (float &value, float &target, float &duration, float time)
{
	if (value == target) {
		duration = 0.0f;
	} else {
		duration -= time;
		if (duration > 0.0f) {

			bool sign0, sign1;

			sign0 = value < target;
			value += ((target - value) * (time / duration));
			if (value == target) {
				duration = 0.0f;
			} else {
				sign1 = value < target;

				// If the value has 'blown past' the target value...
				if (sign0 ^ sign1) {
					duration = 0.0f;
					value		= target;
				}
			}
		} else {
			duration = 0.0f;
			value	   = target;
		}
	}
}


/***********************************************************************************************
 * WeatherMgrClass::WeatherMgrClass --																			  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
WeatherMgrClass::WeatherMgrClass()
{
	Set_Network_ID (NETID_SERVER_WEATHER);
	Set_App_Packet_Type (APPPACKETTYPE_NETWEATHER);
}


/***********************************************************************************************
 * WeatherMgrClass::Init --																						  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
void WeatherMgrClass::Init (SoundEnvironmentClass *soundenvironment)
{
	WWASSERT (soundenvironment != NULL);

	REF_PTR_SET (_SoundEnvironment, soundenvironment);

	_Wind = NULL;
	for (int p = PRECIPITATION_FIRST; p < PRECIPITATION_COUNT; p++) {
		_Precipitation [p] = NULL;
	}

	Reset();
}


/***********************************************************************************************
 * WeatherMgrClass::Reset --																						  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
void WeatherMgrClass::Reset()
{
	int p;

	// Iterate and initialize the parameters.
	for (p = 0; p < PARAMETER_COUNT; p++) {
		_Parameters [p].Initialize();
	}

	if (_Wind != NULL) {
		delete _Wind;
		_Wind = NULL;
	}

	//	Restore the settings to default.
	Set_Wind (0.0f, 0.0f, 0.0f, 0.0f, false);
	for (p = PRECIPITATION_FIRST; p < PRECIPITATION_COUNT; p++) {
		if (_Precipitation [p] != NULL) {
			_Precipitation [p]->Remove();
  			REF_PTR_RELEASE (_Precipitation [p]);
			Set_Precipitation ((PrecipitationEnum) p, 0.0f, 0.0f, false);
		}
	}
	Set_Fog_Enable (false);
	Set_Fog_Range (200.0f, 300.0f);

	_Prime = true;
	_Imported = false;
	_WindOverrideCount = 0;
	_PrecipitationOverrideCount = 0;

	Set_Dirty();
}


/***********************************************************************************************
 * WeatherMgrClass::Shutdown --																					  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
void WeatherMgrClass::Shutdown()
{
	Reset();
	REF_PTR_RELEASE (_SoundEnvironment);
}


/***********************************************************************************************
 * WeatherMgrClass::Set_Wind --																					  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
bool WeatherMgrClass::Set_Wind (float heading, float speed, float variability, float ramptime)
{
	if (CombatManager::I_Am_Server()) {
		if (Set_Wind (heading, speed, variability, ramptime, false)) {
			_TheWeatherMgr.Set_Object_Dirty_Bit (NetworkObjectClass::BIT_RARE, true);
			return (true);
		}
	}
	return (false);
}


/***********************************************************************************************
 * WeatherMgrClass::Override_Wind --																			  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
bool WeatherMgrClass::Override_Wind (float heading, float speed, float variability, float ramptime)
{
	if (CombatManager::I_Am_Server()) {
		_WindOverrideCount++;
		if (Set_Wind (heading, speed, variability, ramptime, true)) {
			_TheWeatherMgr.Set_Object_Dirty_Bit (NetworkObjectClass::BIT_RARE, true);
			return (true);
		}
	}
	return (false);
}


/***********************************************************************************************
 * WeatherMgrClass::Set_Wind --																					  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
bool WeatherMgrClass::Set_Wind (float heading, float speed, float variability, float ramptime, bool override)
{
	if ((heading >= 0.0f) && (heading <= 360.0f) && (speed >= 0.0f) && (variability >= 0.0f) &&
		(variability <= 1.0f) && (ramptime >= 0.0f)) {

		bool o;

		o = (_WindOverrideCount > 0) && override;
		_Parameters [PARAMETER_WIND_HEADING].Set (heading, ramptime, o);
		_Parameters [PARAMETER_WIND_SPEED].Set (speed, ramptime, o);
		_Parameters [PARAMETER_WIND_VARIABILITY].Set (variability, ramptime, o);
		return (true);
	}
	return (false);
}


/***********************************************************************************************
 * WeatherMgrClass::Get_Wind --																					  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
void WeatherMgrClass::Get_Wind (float &heading, float &speed, float &variability)
{
	heading		= _Parameters [PARAMETER_WIND_HEADING].Value();
	speed			= _Parameters [PARAMETER_WIND_SPEED].Value();
	variability = _Parameters [PARAMETER_WIND_VARIABILITY].Value();
}


/***********************************************************************************************
 * WeatherMgrClass::Restore_Wind --																				  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
void WeatherMgrClass::Restore_Wind (float ramptime)
{
	if (CombatManager::I_Am_Server()) {
		_Parameters [PARAMETER_WIND_HEADING].Set (ramptime);
		_Parameters [PARAMETER_WIND_SPEED].Set (ramptime);
		_Parameters [PARAMETER_WIND_VARIABILITY].Set (ramptime);
		_WindOverrideCount--;
		_TheWeatherMgr.Set_Object_Dirty_Bit (NetworkObjectClass::BIT_RARE, true);
	}
}


/***********************************************************************************************
 * WeatherMgrClass::Set_Precipitation --																		  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
bool WeatherMgrClass::Set_Precipitation (PrecipitationEnum precipitation, float density, float ramptime)
{
	if (CombatManager::I_Am_Server()) {
		if (Set_Precipitation (precipitation, density, ramptime, false)) {
			_TheWeatherMgr.Set_Object_Dirty_Bit (NetworkObjectClass::BIT_RARE, true);
			return (true);
		}
	}
	return (false);
}


/***********************************************************************************************
 * WeatherMgrClass::Override_Precipitation --																  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
bool WeatherMgrClass::Override_Precipitation (PrecipitationEnum precipitation, float density, float ramptime)
{
	if (CombatManager::I_Am_Server()) {

		bool success;

		_PrecipitationOverrideCount++;

		// Override requested precipitation but also override and ramp down any other types of precipitation that may exist.
		success = true;
		for (int p = PRECIPITATION_FIRST; p < PRECIPITATION_COUNT; p++) {
			if (p != precipitation) {
				success &= Set_Precipitation ((PrecipitationEnum) p, 0.0f, ramptime, true);
			} else {
				success &= Set_Precipitation ((PrecipitationEnum) p, density, ramptime, true);
			}
		}
		_TheWeatherMgr.Set_Object_Dirty_Bit (NetworkObjectClass::BIT_RARE, true);
		return (success);
	}
	return (false);
}


/***********************************************************************************************
 * WeatherMgrClass::Set_Precipitation --																		  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
bool WeatherMgrClass::Set_Precipitation (PrecipitationEnum precipitation, float density, float ramptime, bool override)
{
	if ((density >= 0.0f) && (ramptime >= 0.0f)) {

		const bool o = (_PrecipitationOverrideCount > 0) && override;

		switch (precipitation) {

			case PRECIPITATION_RAIN:
				_Parameters [PARAMETER_RAIN_DENSITY].Set (density, ramptime, o);
				break;

			case PRECIPITATION_SNOW:
				_Parameters [PARAMETER_SNOW_DENSITY].Set (density, ramptime, o);
				break;

			case PRECIPITATION_ASH:
				_Parameters [PARAMETER_ASH_DENSITY].Set (density, ramptime, o);
				break;

			default:
				WWASSERT (false);
				break;
		}
		return (true);
	}
	return (false);
}


/***********************************************************************************************
 * WeatherMgrClass::Get_Precipitation --																		  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
void WeatherMgrClass::Get_Precipitation (PrecipitationEnum precipitation, float &density)
{
	switch (precipitation) {

		case PRECIPITATION_RAIN:
			density = _Parameters [PARAMETER_RAIN_DENSITY].Value();
			break;

		case PRECIPITATION_SNOW:
			density = _Parameters [PARAMETER_SNOW_DENSITY].Value();
			break;

		case PRECIPITATION_ASH:
			density = _Parameters [PARAMETER_ASH_DENSITY].Value();
			break;

		default:
			WWASSERT (false);
			break;
	}
}


/***********************************************************************************************
 * WeatherMgrClass::Restore_Precipitation --																	  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
void WeatherMgrClass::Restore_Precipitation (float ramptime)
{
	if (CombatManager::I_Am_Server()) {
		_Parameters [PARAMETER_RAIN_DENSITY].Set (ramptime);
		_Parameters [PARAMETER_SNOW_DENSITY].Set (ramptime);
		_Parameters [PARAMETER_ASH_DENSITY].Set (ramptime);
		_PrecipitationOverrideCount--;
		_TheWeatherMgr.Set_Object_Dirty_Bit (NetworkObjectClass::BIT_RARE, true);
	}
}


/***********************************************************************************************
 * WeatherMgrClass::Set_Fog_Range --																			  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   07/12/01    IML : Created.                                                                *
 *=============================================================================================*/
bool WeatherMgrClass::Set_Fog_Range (float startdistance, float enddistance, float ramptime)
{
	if ((startdistance >= 0.0f) && (enddistance >= startdistance)) {
		_Parameters [PARAMETER_FOG_START_DISTANCE].Set (startdistance, ramptime, false);
		_Parameters [PARAMETER_FOG_END_DISTANCE].Set (enddistance, ramptime, false);
		return (true);
	}
	return (false);
}


/***********************************************************************************************
 * WeatherMgrClass::Get_Fog_Range --																			  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   07/12/01    IML : Created.                                                                *
 *=============================================================================================*/
void WeatherMgrClass::Get_Fog_Range (float &startdistance, float &enddistance)
{
	startdistance = _Parameters [PARAMETER_FOG_START_DISTANCE].Value();
	enddistance	  = _Parameters [PARAMETER_FOG_END_DISTANCE].Value();
}


/***********************************************************************************************
 * WeatherMgrClass::Update --																						  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/06/01    IML : Created.                                                                *
 *=============================================================================================*/
void WeatherMgrClass::Update (PhysicsSceneClass *scene, CameraClass *camera)
{
	float	time;
	bool	windmodified, fogmodified;

	time = WW3D::Get_Frame_Time() * 0.001f;

	// Update wind.
	windmodified  = _Parameters [PARAMETER_WIND_HEADING].Update (time, _WindOverrideCount > 0);
	windmodified |= _Parameters [PARAMETER_WIND_SPEED].Update (time, _WindOverrideCount > 0);
	windmodified |= _Parameters [PARAMETER_WIND_VARIABILITY].Update (time, _WindOverrideCount > 0);
	if (_Wind != NULL) {
		if (windmodified) {
			_Wind->Set (_Parameters [PARAMETER_WIND_HEADING].Value(), _Parameters [PARAMETER_WIND_SPEED].Value(), _Parameters [PARAMETER_WIND_VARIABILITY].Value());
		}

		// Optimization: if there is nothing to update, can safely remove the wind.
		if (!_Wind->Update()) {
			delete _Wind;
			_Wind = NULL;
		}

	} else {

	  	// Optimization: only create wind if speed is non-zero.
		if (_Parameters [PARAMETER_WIND_SPEED].Value() > 0.0f) {
			_Wind = new WindClass (_Parameters [PARAMETER_WIND_HEADING].Value(), _Parameters [PARAMETER_WIND_SPEED].Value(), _Parameters [PARAMETER_WIND_VARIABILITY].Value(), _SoundEnvironment);
		}
	}

	for (int p = PRECIPITATION_FIRST; p < PRECIPITATION_COUNT; p++) {

		WeatherParameterClass *parameterptr = NULL;
		bool						  modified;

		switch (p) {

		  	case PRECIPITATION_RAIN:
				parameterptr = &_Parameters [PARAMETER_RAIN_DENSITY];
				break;

		  	case PRECIPITATION_SNOW:
				parameterptr = &_Parameters [PARAMETER_SNOW_DENSITY];
				break;

		  	case PRECIPITATION_ASH:
				parameterptr = &_Parameters [PARAMETER_ASH_DENSITY];
				break;

			default:
				WWASSERT (false);
				break;
		}

		modified = parameterptr->Update (time, _PrecipitationOverrideCount > 0);
	  	if (_Precipitation [p] != NULL) {
	  		if (modified) {
	  			_Precipitation [p]->Set_Density (parameterptr->Value());
	  		}

	  		// Optimization: if there is nothing to update, can safely remove the weather system.
	  		if (!_Precipitation [p]->Update (_Wind, camera->Get_Position())) {
				_Precipitation [p]->Remove();
	  			REF_PTR_RELEASE (_Precipitation [p]);
	  		}

	  	} else {

			// Optimization: only create weather if density is non-zero.
	  	  	if (parameterptr->Value() > 0.0f) {

				// Don't bother on dedicated servers.
				if (!CombatManager::I_Am_Only_Server()) {

					switch (p) {

	  					case PRECIPITATION_RAIN:
	  						_Precipitation [p] = NEW_REF (RainSystemClass, (scene, parameterptr->Value(), _Wind, _SoundEnvironment, _Prime));
	  						break;

	  					case PRECIPITATION_SNOW:
	  						_Precipitation [p] = NEW_REF (SnowSystemClass, (scene, parameterptr->Value(), _Wind, _Prime));
	  						break;

	  					case PRECIPITATION_ASH:
	  						_Precipitation [p] = NEW_REF (AshSystemClass, (scene, parameterptr->Value(), _Wind, _Prime));
	  						break;

						default:
							WWASSERT (false);
							break;
					}
					scene->Add_Render_Object (_Precipitation [p]);
				}
			}
		}
	}

	fogmodified  = _Parameters [PARAMETER_FOG_START_DISTANCE].Update (time, false);
	fogmodified |= _Parameters [PARAMETER_FOG_END_DISTANCE].Update (time, false);
	if (Is_Dirty() || fogmodified) {
		scene->Set_Fog_Enable (_FogEnabled);
		scene->Set_Fog_Range (_Parameters [PARAMETER_FOG_START_DISTANCE].Value(), _Parameters [PARAMETER_FOG_END_DISTANCE].Value());
	}

	if (CombatManager::I_Am_Server()) {

		// Server precipitation can only be primed on the first update iteration of a level.
		_Prime = false;

	} else {

		// Clients can be primed right up until the first import from the server.
		if (_Imported) _Prime = false;
	}

	// Everything necessary has been updated. Clear the dirty flag.
	Set_Dirty (false);
}


/***********************************************************************************************
 * WeatherMgrClass::Save --																						  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/02/01    IML : Created.                                                                *
 *=============================================================================================*/
 #define WRITE_PARAMETER(varname) \
WRITE_MICRO_CHUNK (csave, VARID_ ## varname ## _CURRENT_VALUE, _Parameters [PARAMETER_ ## varname ##].CurrentValue); \
WRITE_MICRO_CHUNK (csave, VARID_ ## varname ## _NORMAL_VALUE, _Parameters [PARAMETER_ ## varname ##].NormalValue);	\
WRITE_MICRO_CHUNK (csave, VARID_ ## varname ## _NORMAL_TARGET, _Parameters [PARAMETER_ ## varname ##].NormalTarget);	\
WRITE_MICRO_CHUNK (csave, VARID_ ## varname ## _NORMAL_DURATION, _Parameters [PARAMETER_ ## varname ##].NormalDuration);	\
WRITE_MICRO_CHUNK (csave, VARID_ ## varname ## _OVERRIDE_TARGET, _Parameters [PARAMETER_ ## varname ##].OverrideTarget);	\
WRITE_MICRO_CHUNK (csave, VARID_ ## varname ## _OVERRIDE_DURATION, _Parameters [PARAMETER_ ## varname ##].OverrideDuration)

bool WeatherMgrClass::Save (ChunkSaveClass &csave)
{
	csave.Begin_Chunk (CHUNKID_MICRO_CHUNKS);
	csave.End_Chunk ();

	Save_Dynamic (csave);
	return (true);
}


/***********************************************************************************************
 * WeatherMgrClass::Save_Dynamic --																						  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/02/01    IML : Created.                                                                *
 *=============================================================================================*/
bool WeatherMgrClass::Save_Dynamic (ChunkSaveClass &csave)
{
	csave.Begin_Chunk (CHUNKID_DYNAMIC_MICRO_CHUNKS);
	WRITE_PARAMETER (WIND_HEADING);
	WRITE_PARAMETER (WIND_SPEED);
	WRITE_PARAMETER (WIND_VARIABILITY);
	WRITE_PARAMETER (RAIN_DENSITY);
	WRITE_PARAMETER (SNOW_DENSITY);
	WRITE_PARAMETER (ASH_DENSITY);
	WRITE_MICRO_CHUNK (csave, VARID_WIND_OVERRIDE_COUNT, _WindOverrideCount);
	WRITE_MICRO_CHUNK (csave, VARID_PRECIPITATION_OVERRIDE_COUNT, _PrecipitationOverrideCount);
	WRITE_MICRO_CHUNK (csave, VARID_FOG_ENABLED, _FogEnabled);
	WRITE_PARAMETER (FOG_START_DISTANCE);
	WRITE_PARAMETER (FOG_END_DISTANCE);
	csave.End_Chunk ();
	return (true);
}


/***********************************************************************************************
 * WeatherMgrClass::Load --																						  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/02/01    IML : Created.                                                                *
 *=============================================================================================*/
bool WeatherMgrClass::Load (ChunkLoadClass &cload)
{
	WWMEMLOG (MEM_GAMEDATA);

	bool retval = true;

	while (cload.Open_Chunk ()) {
		switch (cload.Cur_Chunk_ID ()) {

			case CHUNKID_MICRO_CHUNKS:
				retval &= Load_Micro_Chunks (cload);
				break;

			case CHUNKID_DYNAMIC_MICRO_CHUNKS:
				retval &= Load_Dynamic_Micro_Chunks (cload);
				break;
		}
		cload.Close_Chunk ();
	}
	return (retval);
}


/***********************************************************************************************
 * WeatherMgrClass::Load_Micro_Chunks --																		  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/02/01    IML : Created.                                                                *
 *=============================================================================================*/
#define READ_PARAMETER(varname) \
READ_MICRO_CHUNK (cload, VARID_ ## varname ## _CURRENT_VALUE, _Parameters [PARAMETER_ ## varname ##].CurrentValue); \
READ_MICRO_CHUNK (cload, VARID_ ## varname ## _NORMAL_VALUE, _Parameters [PARAMETER_ ## varname ##].NormalValue);	\
READ_MICRO_CHUNK (cload, VARID_ ## varname ## _NORMAL_TARGET, _Parameters [PARAMETER_ ## varname ##].NormalTarget);	\
READ_MICRO_CHUNK (cload, VARID_ ## varname ## _NORMAL_DURATION, _Parameters [PARAMETER_ ## varname ##].NormalDuration);	\
READ_MICRO_CHUNK (cload, VARID_ ## varname ## _OVERRIDE_TARGET, _Parameters [PARAMETER_ ## varname ##].OverrideTarget);	\
READ_MICRO_CHUNK (cload, VARID_ ## varname ## _OVERRIDE_DURATION, _Parameters [PARAMETER_ ## varname ##].OverrideDuration)

bool WeatherMgrClass::Load_Micro_Chunks (ChunkLoadClass &cload)
{
	// Read weather micro chunk.
	while (cload.Open_Micro_Chunk ()) {
		cload.Close_Micro_Chunk ();
	}

	return (true);
}


/***********************************************************************************************
 * WeatherMgrClass::Load_Dynamic --																						  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/02/01    IML : Created.                                                                *
 *=============================================================================================*/
bool WeatherMgrClass::Load_Dynamic (ChunkLoadClass &cload)
{
	WWMEMLOG (MEM_GAMEDATA);

	bool retval = true;

	while (cload.Open_Chunk ()) {
		switch (cload.Cur_Chunk_ID ()) {

			case CHUNKID_DYNAMIC_MICRO_CHUNKS:
				retval &= Load_Dynamic_Micro_Chunks (cload);
				break;
		}
		cload.Close_Chunk ();
	}
	return (retval);
}


/***********************************************************************************************
 * WeatherMgrClass::Load_Dynamic_Micro_Chunks --																		  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/02/01    IML : Created.                                                                *
 *=============================================================================================*/
bool WeatherMgrClass::Load_Dynamic_Micro_Chunks (ChunkLoadClass &cload)
{
	// Read weather micro chunk.
	while (cload.Open_Micro_Chunk ()) {
		switch (cload.Cur_Micro_Chunk_ID ()) {
			READ_PARAMETER (WIND_HEADING);
			READ_PARAMETER (WIND_SPEED);
			READ_PARAMETER (WIND_VARIABILITY);
			READ_PARAMETER (RAIN_DENSITY);
			READ_PARAMETER (SNOW_DENSITY);
			READ_PARAMETER (ASH_DENSITY);
			READ_MICRO_CHUNK (cload, VARID_WIND_OVERRIDE_COUNT, _WindOverrideCount);
			READ_MICRO_CHUNK (cload, VARID_PRECIPITATION_OVERRIDE_COUNT, _PrecipitationOverrideCount);
			READ_MICRO_CHUNK (cload, VARID_FOG_ENABLED, _FogEnabled);
			READ_PARAMETER (FOG_START_DISTANCE);
			READ_PARAMETER (FOG_END_DISTANCE);
		}
		cload.Close_Micro_Chunk ();
	}

	return (true);
}


/***********************************************************************************************
 * WeatherMgrClass::Export_Rare --																				  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/02/01    IML : Created.                                                                *
 *=============================================================================================*/
#define EXPORT_PARAMETER(object, varname) \
object.Add (_Parameters [PARAMETER_ ## varname ##].NormalTarget); \
object.Add (_Parameters [PARAMETER_ ## varname ##].NormalDuration); \
object.Add (_Parameters [PARAMETER_ ## varname ##].OverrideTarget); \
object.Add (_Parameters [PARAMETER_ ## varname ##].OverrideDuration)

void WeatherMgrClass::Export_Rare (BitStreamClass &packet)
{
	WWASSERT (CombatManager::I_Am_Server());

	EXPORT_PARAMETER (packet, WIND_HEADING);
	EXPORT_PARAMETER (packet, WIND_SPEED);
	EXPORT_PARAMETER (packet, WIND_VARIABILITY);
	EXPORT_PARAMETER (packet, RAIN_DENSITY);
	EXPORT_PARAMETER (packet, SNOW_DENSITY);
	EXPORT_PARAMETER (packet, ASH_DENSITY);
	packet.Add (_WindOverrideCount);
	packet.Add (_PrecipitationOverrideCount);
}


/***********************************************************************************************
 * WeatherMgrClass::Import_Rare --																				  *
 *                                                                                             *
 * INPUT:                                                                                      *
 *                                                                                             *
 * OUTPUT:                                                                                     *
 *                                                                                             *
 * WARNINGS:                                                                                   *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   03/02/01    IML : Created.                                                                *
 *=============================================================================================*/
#define IMPORT_PARAMETER(object, varname) \
object.Get (_Parameters [PARAMETER_ ## varname ##].NormalTarget); \
object.Get (_Parameters [PARAMETER_ ## varname ##].NormalDuration); \
object.Get (_Parameters [PARAMETER_ ## varname ##].OverrideTarget); \
object.Get (_Parameters [PARAMETER_ ## varname ##].OverrideDuration)

void WeatherMgrClass::Import_Rare (BitStreamClass &packet)
{
	WWASSERT (CombatManager::I_Am_Client());

	IMPORT_PARAMETER (packet, WIND_HEADING);
	IMPORT_PARAMETER (packet, WIND_SPEED);
	IMPORT_PARAMETER (packet, WIND_VARIABILITY);
	IMPORT_PARAMETER (packet, RAIN_DENSITY);
	IMPORT_PARAMETER (packet, SNOW_DENSITY);
	IMPORT_PARAMETER (packet, ASH_DENSITY);
	packet.Get (_WindOverrideCount);
	packet.Get (_PrecipitationOverrideCount);

	// Flag that weather data has been imported.
	_Imported = true;
}


#undef EXPORT_PARAMETER
#undef IMPORT_PARAMETER
#undef READ_PARAMETER
#undef WRITE_PARAMETER