This repository has been archived on 2025-02-27. You can view files and clone it, but cannot push or open issues or pull requests.
CnC_Renegade/Code/Combat/WeatherMgr.cpp

2378 lines
108 KiB
C++
Raw Permalink Normal View History

/*
** 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