/*
===========================================================================
Copyright (C) 2000 - 2013, Raven Software, Inc.
Copyright (C) 2001 - 2013, Activision, Inc.
Copyright (C) 2013 - 2015, OpenJK contributors
This file is part of the OpenJK source code.
OpenJK is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
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 .
===========================================================================
*/
////////////////////////////////////////////////////////////////////////////////////////
// RAVEN SOFTWARE - STAR WARS: JK II
// (c) 2002 Activision
//
// World Effects
//
//
////////////////////////////////////////////////////////////////////////////////////////
#include "../server/exe_headers.h"
////////////////////////////////////////////////////////////////////////////////////////
// Externs & Fwd Decl.
////////////////////////////////////////////////////////////////////////////////////////
extern void SetViewportAndScissor( void );
////////////////////////////////////////////////////////////////////////////////////////
// Includes
////////////////////////////////////////////////////////////////////////////////////////
#include "tr_local.h"
#include "tr_WorldEffects.h"
#include "../Ravl/CVec.h"
#include "../Ratl/vector_vs.h"
#include "../Ratl/bits_vs.h"
////////////////////////////////////////////////////////////////////////////////////////
// Defines
////////////////////////////////////////////////////////////////////////////////////////
#define GLS_ALPHA (GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA)
#define MAX_WIND_ZONES 12
#define MAX_WEATHER_ZONES 50 // so we can more zones that are smaller
#define MAX_PUFF_SYSTEMS 2
#define MAX_PARTICLE_CLOUDS 5
#define POINTCACHE_CELL_SIZE 32.0f
////////////////////////////////////////////////////////////////////////////////////////
// Globals
////////////////////////////////////////////////////////////////////////////////////////
float mMillisecondsElapsed = 0;
float mSecondsElapsed = 0;
bool mFrozen = false;
CVec3 mGlobalWindVelocity;
CVec3 mGlobalWindDirection;
float mGlobalWindSpeed;
int mParticlesRendered;
////////////////////////////////////////////////////////////////////////////////////////
// Handy Functions
////////////////////////////////////////////////////////////////////////////////////////
inline void VectorMA( vec3_t vecAdd, const float scale, const vec3_t vecScale)
{
vecAdd[0] += (scale * vecScale[0]);
vecAdd[1] += (scale * vecScale[1]);
vecAdd[2] += (scale * vecScale[2]);
}
inline void VectorFloor(vec3_t in)
{
in[0] = floorf(in[0]);
in[1] = floorf(in[1]);
in[2] = floorf(in[2]);
}
inline void VectorCeil(vec3_t in)
{
in[0] = ceilf(in[0]);
in[1] = ceilf(in[1]);
in[2] = ceilf(in[2]);
}
inline float FloatRand(void)
{
return ((float)rand() / (float)RAND_MAX);
}
inline float fast_flrand(float min, float max)
{
//return min + (max - min) * flrand;
return Q_flrand(min, max); //fixme?
}
inline void SnapFloatToGrid(float& f, int GridSize)
{
f = (int)(f);
bool fNeg = (f<0);
if (fNeg)
{
f *= -1; // Temporarly make it positive
}
int Offset = ((int)(f) % (int)(GridSize));
int OffsetAbs = abs(Offset);
if (OffsetAbs>(GridSize/2))
{
Offset = (GridSize - OffsetAbs) * -1;
}
f -= Offset;
if (fNeg)
{
f *= -1; // Put It Back To Negative
}
f = (int)(f);
assert(((int)(f)%(int)(GridSize)) == 0);
}
inline void SnapVectorToGrid(CVec3& Vec, int GridSize)
{
SnapFloatToGrid(Vec[0], GridSize);
SnapFloatToGrid(Vec[1], GridSize);
SnapFloatToGrid(Vec[2], GridSize);
}
////////////////////////////////////////////////////////////////////////////////////////
// Range Structures
////////////////////////////////////////////////////////////////////////////////////////
struct SVecRange
{
CVec3 mMins;
CVec3 mMaxs;
inline void Clear()
{
mMins.Clear();
mMaxs.Clear();
}
inline void Pick(CVec3& V)
{
V[0] = Q_flrand(mMins[0], mMaxs[0]);
V[1] = Q_flrand(mMins[1], mMaxs[1]);
V[2] = Q_flrand(mMins[2], mMaxs[2]);
}
inline void Wrap(CVec3& V)
{
if (V[0]<=mMins[0])
{
if ((mMins[0]-V[0])>500)
{
Pick(V);
return;
}
V[0] = mMaxs[0] - 10.0f;
}
if (V[0]>=mMaxs[0])
{
if ((V[0]-mMaxs[0])>500)
{
Pick(V);
return;
}
V[0] = mMins[0] + 10.0f;
}
if (V[1]<=mMins[1])
{
if ((mMins[1]-V[1])>500)
{
Pick(V);
return;
}
V[1] = mMaxs[1] - 10.0f;
}
if (V[1]>=mMaxs[1])
{
if ((V[1]-mMaxs[1])>500)
{
Pick(V);
return;
}
V[1] = mMins[1] + 10.0f;
}
if (V[2]<=mMins[2])
{
if ((mMins[2]-V[2])>500)
{
Pick(V);
return;
}
V[2] = mMaxs[2] - 10.0f;
}
if (V[2]>=mMaxs[2])
{
if ((V[2]-mMaxs[2])>500)
{
Pick(V);
return;
}
V[2] = mMins[2] + 10.0f;
}
}
inline bool In(const CVec3& V)
{
return (V>mMins && VmMin && VmMin && V TFlags;
float mAlpha;
TFlags mFlags;
CVec3 mPosition;
CVec3 mVelocity;
float mMass; // A higher number will more greatly resist force and result in greater gravity
};
////////////////////////////////////////////////////////////////////////////////////////
// The Wind
////////////////////////////////////////////////////////////////////////////////////////
class CWindZone
{
public:
bool mGlobal;
SVecRange mRBounds;
SVecRange mRVelocity;
SIntRange mRDuration;
SIntRange mRDeadTime;
float mMaxDeltaVelocityPerUpdate;
float mChanceOfDeadTime;
CVec3 mCurrentVelocity;
CVec3 mTargetVelocity;
int mTargetVelocityTimeRemaining;
public:
////////////////////////////////////////////////////////////////////////////////////
// Initialize - Will setup default values for all data
////////////////////////////////////////////////////////////////////////////////////
void Initialize()
{
mRBounds.Clear();
mGlobal = true;
mRVelocity.mMins = -1500.0f;
mRVelocity.mMins[2] = -10.0f;
mRVelocity.mMaxs = 1500.0f;
mRVelocity.mMaxs[2] = 10.0f;
mMaxDeltaVelocityPerUpdate = 10.0f;
mRDuration.mMin = 1000;
mRDuration.mMax = 2000;
mChanceOfDeadTime = 0.3f;
mRDeadTime.mMin = 1000;
mRDeadTime.mMax = 3000;
mCurrentVelocity.Clear();
mTargetVelocity.Clear();
mTargetVelocityTimeRemaining = 0;
}
////////////////////////////////////////////////////////////////////////////////////
// Update - Changes wind when current target velocity expires
////////////////////////////////////////////////////////////////////////////////////
void Update()
{
if (mTargetVelocityTimeRemaining==0)
{
if (FloatRand() mMaxDeltaVelocityPerUpdate)
{
DeltaVelocityLen = mMaxDeltaVelocityPerUpdate;
}
DeltaVelocity *= (DeltaVelocityLen);
mCurrentVelocity += DeltaVelocity;
}
}
};
ratl::vector_vs mWindZones;
ratl::vector_vs mLocalWindZones;
bool R_GetWindVector(vec3_t windVector, vec3_t atpoint)
{
VectorCopy(mGlobalWindDirection.v, windVector);
if (atpoint && mLocalWindZones.size())
{
for (int curLocalWindZone=0; curLocalWindZonemRBounds.In(atpoint))
{
VectorAdd(windVector, mLocalWindZones[curLocalWindZone]->mCurrentVelocity.v, windVector);
}
}
VectorNormalize(windVector);
}
return true;
}
bool R_GetWindSpeed(float &windSpeed, vec3_t atpoint)
{
windSpeed = mGlobalWindSpeed;
if (atpoint && mLocalWindZones.size())
{
for (int curLocalWindZone=0; curLocalWindZonemRBounds.In(atpoint))
{
windSpeed += VectorLength(mLocalWindZones[curLocalWindZone]->mCurrentVelocity.v);
}
}
}
return true;
}
bool R_GetWindGusting(vec3_t atpoint)
{
float windSpeed;
R_GetWindSpeed(windSpeed, atpoint);
return (windSpeed>1000.0f);
}
////////////////////////////////////////////////////////////////////////////////////////
// Outside Point Cache
////////////////////////////////////////////////////////////////////////////////////////
class COutside
{
#define COUTSIDE_STRUCT_VERSION 1 // you MUST increase this any time you change any binary (fields) inside this class, or cahced files will fuck up
public:
////////////////////////////////////////////////////////////////////////////////////
//Global Public Outside Variables
////////////////////////////////////////////////////////////////////////////////////
bool mOutsideShake;
float mOutsidePain;
CVec3 mFogColor;
int mFogColorInt;
bool mFogColorTempActive;
private:
////////////////////////////////////////////////////////////////////////////////////
// The Outside Cache
////////////////////////////////////////////////////////////////////////////////////
bool mCacheInit; // Has It Been Cached?
struct SWeatherZone
{
static bool mMarkedOutside;
uint32_t *mPointCache; // malloc block ptr
int miPointCacheByteSize; // size of block
SVecRange mExtents;
SVecRange mSize;
int mWidth;
int mHeight;
int mDepth;
void WriteToDisk( fileHandle_t f )
{
ri.FS_Write(&mMarkedOutside,sizeof(mMarkedOutside),f);
ri.FS_Write( mPointCache, miPointCacheByteSize, f );
}
void ReadFromDisk( fileHandle_t f )
{
ri.FS_Read(&mMarkedOutside,sizeof(mMarkedOutside),f);
ri.FS_Read( mPointCache, miPointCacheByteSize, f);
}
////////////////////////////////////////////////////////////////////////////////////
// Convert To Cell
////////////////////////////////////////////////////////////////////////////////////
inline void ConvertToCell(const CVec3& pos, int& x, int& y, int& z, int& bit)
{
x = (int)((pos[0] / POINTCACHE_CELL_SIZE) - mSize.mMins[0]);
y = (int)((pos[1] / POINTCACHE_CELL_SIZE) - mSize.mMins[1]);
z = (int)((pos[2] / POINTCACHE_CELL_SIZE) - mSize.mMins[2]);
bit = (z & 31);
z >>= 5;
}
////////////////////////////////////////////////////////////////////////////////////
// CellOutside - Test to see if a given cell is outside
////////////////////////////////////////////////////////////////////////////////////
inline bool CellOutside(int x, int y, int z, int bit)
{
if ((x < 0 || x >= mWidth) || (y < 0 || y >= mHeight) || (z < 0 || z >= mDepth) || (bit < 0 || bit >= 32))
{
return !(mMarkedOutside);
}
return (mMarkedOutside==(!!(mPointCache[((z * mWidth * mHeight) + (y * mWidth) + x)]&(1 << bit))));
}
};
ratl::vector_vs mWeatherZones;
private:
////////////////////////////////////////////////////////////////////////////////////
// Iteration Variables
////////////////////////////////////////////////////////////////////////////////////
int mWCells;
int mHCells;
int mXCell;
int mYCell;
int mZBit;
int mXMax;
int mYMax;
int mZMax;
private:
////////////////////////////////////////////////////////////////////////////////////
// Contents Outside
////////////////////////////////////////////////////////////////////////////////////
inline bool ContentsOutside(int contents)
{
if (contents&CONTENTS_WATER || contents&CONTENTS_SOLID)
{
return false;
}
if (mCacheInit)
{
if (SWeatherZone::mMarkedOutside)
{
return (!!(contents&CONTENTS_OUTSIDE));
}
return (!(contents&CONTENTS_INSIDE));
}
return !!(contents&CONTENTS_OUTSIDE);
}
public:
////////////////////////////////////////////////////////////////////////////////////
// Constructor - Will setup default values for all data
////////////////////////////////////////////////////////////////////////////////////
void Reset()
{
mOutsideShake = false;
mOutsidePain = 0.0;
mCacheInit = false;
SWeatherZone::mMarkedOutside = false;
mFogColor.Clear();
mFogColorInt = 0;
mFogColorTempActive = false;
for (int wz=0; wz> 5;
Wz.miPointCacheByteSize = (Wz.mWidth * Wz.mHeight * Wz.mDepth) * sizeof(uint32_t);
Wz.mPointCache = (uint32_t *)R_Malloc( Wz.miPointCacheByteSize, TAG_POINTCACHE, qtrue );
}
else
{
assert("MaxWeatherZones Hit!"==0);
}
}
const char *GenCachedWeatherFilename(void)
{
return va("maps/%s.weather", sv_mapname->string);
}
// weather file format...
//
struct WeatherFileHeader_t
{
int m_iVersion;
int m_iChecksum;
WeatherFileHeader_t()
{
m_iVersion = COUTSIDE_STRUCT_VERSION;
m_iChecksum = sv_mapChecksum->integer;
}
};
fileHandle_t WriteCachedWeatherFile( void )
{
fileHandle_t f = ri.FS_FOpenFileWrite( GenCachedWeatherFilename(), qtrue );
if (f)
{
WeatherFileHeader_t WeatherFileHeader;
ri.FS_Write(&WeatherFileHeader, sizeof(WeatherFileHeader), f);
return f;
}
else
{
ri.Printf( PRINT_WARNING, "(Unable to open weather file \"%s\" for writing!)\n",GenCachedWeatherFilename());
}
return 0;
}
// returns 0 for not-found or invalid file, else open handle to continue read from (which you then close yourself)...
//
fileHandle_t ReadCachedWeatherFile( void )
{
fileHandle_t f = 0;
ri.FS_FOpenFileRead( GenCachedWeatherFilename(), &f, qfalse );
if ( f )
{
// ok, it exists, but is it valid for this map?...
//
WeatherFileHeader_t WeatherFileHeaderForCompare;
WeatherFileHeader_t WeatherFileHeaderFromDisk;
ri.FS_Read(&WeatherFileHeaderFromDisk, sizeof(WeatherFileHeaderFromDisk), f);
if (!memcmp(&WeatherFileHeaderForCompare, &WeatherFileHeaderFromDisk, sizeof(WeatherFileHeaderFromDisk)))
{
// go for it...
//
return f;
}
ri.Printf( PRINT_WARNING, "( Cached weather file \"%s\" out of date, regenerating... )\n",GenCachedWeatherFilename());
ri.FS_FCloseFile( f );
}
else
{
ri.Printf( PRINT_WARNING, "( No cached weather file found, generating... )\n");
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////////
// Cache - Will Scan the World, Creating The Cache
////////////////////////////////////////////////////////////////////////////////////
void Cache()
{
if (!tr.world || mCacheInit)
{
return;
}
// all this piece of code does really is fill in the bool "SWeatherZone::mMarkedOutside", plus the mPointCache[] for each zone,
// so we can diskload those. Maybe.
fileHandle_t f = ReadCachedWeatherFile();
if ( f )
{
for (int iZone=0; iZonebmodels[0].bounds[0], tr.world->bmodels[0].bounds[1]);
}
f = WriteCachedWeatherFile();
// Iterate Over All Weather Zones
//--------------------------------
for (int zone=0; zoneglobalFog != -1)
{
// If Non Zero, Try To Set The Color
//-----------------------------------
if (color[0] || color[1] || color[2])
{
// Remember The Normal Fog Color
//-------------------------------
if (!mOutside.mFogColorTempActive)
{
mOutside.mFogColor = tr.world->fogs[tr.world->globalFog].parms.color;
mOutside.mFogColorInt = tr.world->fogs[tr.world->globalFog].colorInt;
mOutside.mFogColorTempActive = true;
}
// Set The New One
//-----------------
tr.world->fogs[tr.world->globalFog].parms.color[0] = color[0];
tr.world->fogs[tr.world->globalFog].parms.color[1] = color[1];
tr.world->fogs[tr.world->globalFog].parms.color[2] = color[2];
tr.world->fogs[tr.world->globalFog].colorInt = ColorBytes4 (
color[0] * tr.identityLight,
color[1] * tr.identityLight,
color[2] * tr.identityLight,
1.0 );
}
// If Unable TO Parse The Command Color Vector, Restore The Previous Fog Color
//-----------------------------------------------------------------------------
else if (mOutside.mFogColorTempActive)
{
mOutside.mFogColorTempActive = false;
tr.world->fogs[tr.world->globalFog].parms.color[0] = mOutside.mFogColor[0];
tr.world->fogs[tr.world->globalFog].parms.color[1] = mOutside.mFogColor[1];
tr.world->fogs[tr.world->globalFog].parms.color[2] = mOutside.mFogColor[2];
tr.world->fogs[tr.world->globalFog].colorInt = mOutside.mFogColorInt;
}
}
return true;
}
////////////////////////////////////////////////////////////////////////////////////////
// Particle Cloud
////////////////////////////////////////////////////////////////////////////////////////
class CParticleCloud
{
private:
////////////////////////////////////////////////////////////////////////////////////
// DYNAMIC MEMORY
////////////////////////////////////////////////////////////////////////////////////
image_t* mImage;
WFXParticle* mParticles;
private:
////////////////////////////////////////////////////////////////////////////////////
// RUN TIME VARIANTS
////////////////////////////////////////////////////////////////////////////////////
float mSpawnSpeed;
CVec3 mSpawnPlaneNorm;
CVec3 mSpawnPlaneRight;
CVec3 mSpawnPlaneUp;
SVecRange mRange;
CVec3 mCameraPosition;
CVec3 mCameraForward;
CVec3 mCameraLeft;
CVec3 mCameraDown;
CVec3 mCameraLeftPlusUp;
CVec3 mCameraLeftMinusUp;
int mParticleCountRender;
int mGLModeEnum;
bool mPopulated;
public:
////////////////////////////////////////////////////////////////////////////////////
// CONSTANTS
////////////////////////////////////////////////////////////////////////////////////
bool mOrientWithVelocity;
float mSpawnPlaneSize;
float mSpawnPlaneDistance;
SVecRange mSpawnRange;
float mGravity; // How much gravity affects the velocity of a particle
CVec4 mColor; // RGBA color
int mVertexCount; // 3 for triangle, 4 for quad, other numbers not supported
float mWidth;
float mHeight;
int mBlendMode; // 0 = ALPHA, 1 = SRC->SRC
int mFilterMode; // 0 = LINEAR, 1 = NEAREST
float mFade; // How much to fade in and out 1.0 = instant, 0.01 = very slow
SFloatRange mRotation;
float mRotationDelta;
float mRotationDeltaTarget;
float mRotationCurrent;
SIntRange mRotationChangeTimer;
int mRotationChangeNext;
SFloatRange mMass; // Determines how slowness to accelerate, higher number = slower
float mFrictionInverse; // How much air friction does this particle have 1.0=none, 0.0=nomove
int mParticleCount;
bool mWaterParticles;
public:
////////////////////////////////////////////////////////////////////////////////////
// Initialize - Create Image, Particles, And Setup All Values
////////////////////////////////////////////////////////////////////////////////////
void Initialize(int count, const char* texturePath, int VertexCount=4)
{
Reset();
assert(mParticleCount==0 && mParticles==0);
assert(mImage==0);
// Create The Image
//------------------
mImage = R_FindImageFile(texturePath, qfalse, qfalse, qfalse, GL_CLAMP);
if (!mImage)
{
Com_Error(ERR_DROP, "CParticleCloud: Could not texture %s", texturePath);
}
GL_Bind(mImage);
// Create The Particles
//----------------------
mParticleCount = count;
mParticles = new WFXParticle[mParticleCount];
WFXParticle* part=0;
for (int particleNum=0; particleNummPosition.Clear();
part->mVelocity.Clear();
part->mAlpha = 0.0f;
mMass.Pick(part->mMass);
}
mVertexCount = VertexCount;
mGLModeEnum = (mVertexCount==3)?(GL_TRIANGLES):(GL_QUADS);
}
////////////////////////////////////////////////////////////////////////////////////
// Reset - Initializes all data to default values
////////////////////////////////////////////////////////////////////////////////////
void Reset()
{
if (mImage)
{
// TODO: Free Image?
}
mImage = 0;
if (mParticleCount)
{
delete [] mParticles;
}
mParticleCount = 0;
mParticles = 0;
mPopulated = 0;
// These Are The Default Startup Values For Constant Data
//========================================================
mOrientWithVelocity = false;
mWaterParticles = false;
mSpawnPlaneDistance = 500;
mSpawnPlaneSize = 500;
mSpawnRange.mMins = -(mSpawnPlaneDistance*1.25f);
mSpawnRange.mMaxs = (mSpawnPlaneDistance*1.25f);
mGravity = 300.0f; // Units Per Second
mColor = 1.0f;
mVertexCount = 4;
mWidth = 1.0f;
mHeight = 1.0f;
mBlendMode = 0;
mFilterMode = 0;
mFade = 10.0f;
mRotation.Clear();
mRotationDelta = 0.0f;
mRotationDeltaTarget= 0.0f;
mRotationCurrent = 0.0f;
mRotationChangeNext = -1;
mRotation.mMin = -0.7f;
mRotation.mMax = 0.7f;
mRotationChangeTimer.mMin = 500;
mRotationChangeTimer.mMax = 2000;
mMass.mMin = 5.0f;
mMass.mMax = 10.0f;
mFrictionInverse = 0.7f; // No Friction?
}
////////////////////////////////////////////////////////////////////////////////////
// Constructor - Will setup default values for all data
////////////////////////////////////////////////////////////////////////////////////
CParticleCloud()
{
mImage = 0;
mParticleCount = 0;
Reset();
}
////////////////////////////////////////////////////////////////////////////////////
// Initialize - Will setup default values for all data
////////////////////////////////////////////////////////////////////////////////////
~CParticleCloud()
{
Reset();
}
////////////////////////////////////////////////////////////////////////////////////
// UseSpawnPlane - Check To See If We Should Spawn On A Plane, Or Just Wrap The Box
////////////////////////////////////////////////////////////////////////////////////
inline bool UseSpawnPlane()
{
return (mGravity!=0.0f);
}
////////////////////////////////////////////////////////////////////////////////////
// Update - Applies All Physics Forces To All Contained Particles
////////////////////////////////////////////////////////////////////////////////////
void Update()
{
WFXParticle* part=0;
CVec3 partForce;
CVec3 partMoved;
CVec3 partToCamera;
bool partRendering;
bool partOutside;
bool partInRange;
bool partInView;
int particleNum;
float particleFade = (mFade * mSecondsElapsed);
int numLocalWindZones = mLocalWindZones.size();
int curLocalWindZone;
// Compute Camera
//----------------
{
mCameraPosition = backEnd.viewParms.ori.origin;
mCameraForward = backEnd.viewParms.ori.axis[0];
mCameraLeft = backEnd.viewParms.ori.axis[1];
mCameraDown = backEnd.viewParms.ori.axis[2];
if (mRotationChangeNext!=-1)
{
if (mRotationChangeNext==0)
{
mRotation.Pick(mRotationDeltaTarget);
mRotationChangeTimer.Pick(mRotationChangeNext);
if (mRotationChangeNext<=0)
{
mRotationChangeNext = 1;
}
}
mRotationChangeNext--;
float RotationDeltaDifference = (mRotationDeltaTarget - mRotationDelta);
if (fabsf(RotationDeltaDifference)>0.01)
{
mRotationDelta += RotationDeltaDifference; // Blend To New Delta
}
mRotationCurrent += (mRotationDelta * mSecondsElapsed);
float s = sinf(mRotationCurrent);
float c = cosf(mRotationCurrent);
CVec3 TempCamLeft(mCameraLeft);
mCameraLeft *= (c * mWidth);
mCameraLeft.ScaleAdd(mCameraDown, (s * mWidth * -1.0f));
mCameraDown *= (c * mHeight);
mCameraDown.ScaleAdd(TempCamLeft, (s * mHeight));
}
else
{
mCameraLeft *= mWidth;
mCameraDown *= mHeight;
}
}
// Compute Global Force
//----------------------
CVec3 force;
{
force.Clear();
// Apply Gravity
//---------------
force[2] = -1.0f * mGravity;
// Apply Wind Velocity
//---------------------
force += mGlobalWindVelocity;
}
// Update Range
//--------------
{
mRange.mMins = mCameraPosition + mSpawnRange.mMins;
mRange.mMaxs = mCameraPosition + mSpawnRange.mMaxs;
// If Using A Spawn Plane, Increase The Range Box A Bit To Account For Rotation On The Spawn Plane
//-------------------------------------------------------------------------------------------------
if (UseSpawnPlane())
{
for (int dim=0; dim<3; dim++)
{
if (force[dim]>0.01)
{
mRange.mMins[dim] -= (mSpawnPlaneDistance/2.0f);
}
else if (force[dim]<-0.01)
{
mRange.mMaxs[dim] += (mSpawnPlaneDistance/2.0f);
}
}
mSpawnPlaneNorm = force;
mSpawnSpeed = VectorNormalize(mSpawnPlaneNorm.v);
MakeNormalVectors(mSpawnPlaneNorm.v, mSpawnPlaneRight.v, mSpawnPlaneUp.v);
}
// Optimization For Quad Position Calculation
//--------------------------------------------
if (mVertexCount==4)
{
mCameraLeftPlusUp = (mCameraLeft - mCameraDown);
mCameraLeftMinusUp = (mCameraLeft + mCameraDown);
}
else
{
mCameraLeftPlusUp = (mCameraDown + mCameraLeft); // should really be called mCamera Left + Down
}
}
// Stop All Additional Processing
//--------------------------------
if (mFrozen)
{
return;
}
// Now Update All Particles
//--------------------------
mParticleCountRender = 0;
for (particleNum=0; particleNummPosition); // First Time Spawn Location
}
// Grab The Force And Apply Non Global Wind
//------------------------------------------
partForce = force;
if (numLocalWindZones)
{
for (curLocalWindZone=0; curLocalWindZonemRBounds.In(part->mPosition))
{
partForce += mLocalWindZones[curLocalWindZone]->mCurrentVelocity;
}
}
}
partForce /= part->mMass;
// Apply The Force
//-----------------
part->mVelocity += partForce;
part->mVelocity *= mFrictionInverse;
part->mPosition.ScaleAdd(part->mVelocity, mSecondsElapsed);
partToCamera = (part->mPosition - mCameraPosition);
partRendering = part->mFlags.get_bit(WFXParticle::FLAG_RENDER);
partOutside = mOutside.PointOutside(part->mPosition, mWidth, mHeight);
partInRange = mRange.In(part->mPosition);
partInView = (partOutside && partInRange && (partToCamera.Dot(mCameraForward)>0.0f));
// Process Respawn
//-----------------
if (!partInRange && !partRendering)
{
part->mVelocity.Clear();
// Reselect A Position On The Spawn Plane
//----------------------------------------
if (UseSpawnPlane())
{
part->mPosition = mCameraPosition;
part->mPosition -= (mSpawnPlaneNorm* mSpawnPlaneDistance);
part->mPosition += (mSpawnPlaneRight*Q_flrand(-mSpawnPlaneSize, mSpawnPlaneSize));
part->mPosition += (mSpawnPlaneUp* Q_flrand(-mSpawnPlaneSize, mSpawnPlaneSize));
}
// Otherwise, Just Wrap Around To The Other End Of The Range
//-----------------------------------------------------------
else
{
mRange.Wrap(part->mPosition);
}
partInRange = true;
}
// Process Fade
//--------------
{
// Start A Fade Out
//------------------
if (partRendering && !partInView)
{
part->mFlags.clear_bit(WFXParticle::FLAG_FADEIN);
part->mFlags.set_bit(WFXParticle::FLAG_FADEOUT);
}
// Switch From Fade Out To Fade In
//---------------------------------
else if (partRendering && partInView && part->mFlags.get_bit(WFXParticle::FLAG_FADEOUT))
{
part->mFlags.set_bit(WFXParticle::FLAG_FADEIN);
part->mFlags.clear_bit(WFXParticle::FLAG_FADEOUT);
}
// Start A Fade In
//-----------------
else if (!partRendering && partInView)
{
partRendering = true;
part->mAlpha = 0.0f;
part->mFlags.set_bit(WFXParticle::FLAG_RENDER);
part->mFlags.set_bit(WFXParticle::FLAG_FADEIN);
part->mFlags.clear_bit(WFXParticle::FLAG_FADEOUT);
}
// Update Fade
//-------------
if (partRendering)
{
// Update Fade Out
//-----------------
if (part->mFlags.get_bit(WFXParticle::FLAG_FADEOUT))
{
part->mAlpha -= particleFade;
if (part->mAlpha<=0.0f)
{
part->mAlpha = 0.0f;
part->mFlags.clear_bit(WFXParticle::FLAG_FADEOUT);
part->mFlags.clear_bit(WFXParticle::FLAG_FADEIN);
part->mFlags.clear_bit(WFXParticle::FLAG_RENDER);
partRendering = false;
}
}
// Update Fade In
//----------------
else if (part->mFlags.get_bit(WFXParticle::FLAG_FADEIN))
{
part->mAlpha += particleFade;
if (part->mAlpha>=mColor[3])
{
part->mFlags.clear_bit(WFXParticle::FLAG_FADEIN);
part->mAlpha = mColor[3];
}
}
}
}
// Keep Track Of The Number Of Particles To Render
//-------------------------------------------------
if (part->mFlags.get_bit(WFXParticle::FLAG_RENDER))
{
mParticleCountRender ++;
}
}
mPopulated = true;
}
////////////////////////////////////////////////////////////////////////////////////
// Render -
////////////////////////////////////////////////////////////////////////////////////
void Render()
{
WFXParticle* part=0;
int particleNum;
CVec3 partDirection;
// Set The GL State And Image Binding
//------------------------------------
GL_State((mBlendMode==0)?(GLS_ALPHA):(GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE));
GL_Bind(mImage);
// Enable And Disable Things
//---------------------------
qglEnable(GL_TEXTURE_2D);
qglDisable(GL_CULL_FACE);
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (mFilterMode==0)?(GL_LINEAR):(GL_NEAREST));
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (mFilterMode==0)?(GL_LINEAR):(GL_NEAREST));
// Setup Matrix Mode And Translation
//-----------------------------------
qglMatrixMode(GL_MODELVIEW);
qglPushMatrix();
// Begin
//-------
qglBegin(mGLModeEnum);
for (particleNum=0; particleNummFlags.get_bit(WFXParticle::FLAG_RENDER))
{
continue;
}
// If Oriented With Velocity, We Want To Calculate Vertx Offsets Differently For Each Particle
//---------------------------------------------------------------------------------------------
if (mOrientWithVelocity)
{
partDirection = part->mVelocity;
VectorNormalize(partDirection.v);
mCameraDown = partDirection;
mCameraDown *= (mHeight * -1);
if (mVertexCount==4)
{
mCameraLeftPlusUp = (mCameraLeft - mCameraDown);
mCameraLeftMinusUp = (mCameraLeft + mCameraDown);
}
else
{
mCameraLeftPlusUp = (mCameraDown + mCameraLeft);
}
}
// Blend Mode Zero -> Apply Alpha Just To Alpha Channel
//------------------------------------------------------
if (mBlendMode==0)
{
qglColor4f(mColor[0], mColor[1], mColor[2], part->mAlpha);
}
// Otherwise Apply Alpha To All Channels
//---------------------------------------
else
{
qglColor4f(mColor[0]*part->mAlpha, mColor[1]*part->mAlpha, mColor[2]*part->mAlpha, mColor[3]*part->mAlpha);
}
// Render A Triangle
//-------------------
if (mVertexCount==3)
{
qglTexCoord2f(1.0, 0.0);
qglVertex3f(part->mPosition[0],
part->mPosition[1],
part->mPosition[2]);
qglTexCoord2f(0.0, 1.0);
qglVertex3f(part->mPosition[0] + mCameraLeft[0],
part->mPosition[1] + mCameraLeft[1],
part->mPosition[2] + mCameraLeft[2]);
qglTexCoord2f(0.0, 0.0);
qglVertex3f(part->mPosition[0] + mCameraLeftPlusUp[0],
part->mPosition[1] + mCameraLeftPlusUp[1],
part->mPosition[2] + mCameraLeftPlusUp[2]);
}
// Render A Quad
//---------------
else
{
// Left bottom.
qglTexCoord2f( 0.0, 0.0 );
qglVertex3f(part->mPosition[0] - mCameraLeftMinusUp[0],
part->mPosition[1] - mCameraLeftMinusUp[1],
part->mPosition[2] - mCameraLeftMinusUp[2] );
// Right bottom.
qglTexCoord2f( 1.0, 0.0 );
qglVertex3f(part->mPosition[0] - mCameraLeftPlusUp[0],
part->mPosition[1] - mCameraLeftPlusUp[1],
part->mPosition[2] - mCameraLeftPlusUp[2] );
// Right top.
qglTexCoord2f( 1.0, 1.0 );
qglVertex3f(part->mPosition[0] + mCameraLeftMinusUp[0],
part->mPosition[1] + mCameraLeftMinusUp[1],
part->mPosition[2] + mCameraLeftMinusUp[2] );
// Left top.
qglTexCoord2f( 0.0, 1.0 );
qglVertex3f(part->mPosition[0] + mCameraLeftPlusUp[0],
part->mPosition[1] + mCameraLeftPlusUp[1],
part->mPosition[2] + mCameraLeftPlusUp[2] );
}
}
qglEnd();
qglEnable(GL_CULL_FACE);
qglPopMatrix();
mParticlesRendered += mParticleCountRender;
}
};
ratl::vector_vs mParticleClouds;
////////////////////////////////////////////////////////////////////////////////////////
// Init World Effects - Will Iterate Over All Particle Clouds, Clear Them Out, And Erase
////////////////////////////////////////////////////////////////////////////////////////
void R_InitWorldEffects(void)
{
for (int i=0; i1000.0f)
{
mMillisecondsElapsed = 1000.0f;
}
mSecondsElapsed = (mMillisecondsElapsed / 1000.0f);
// Make Sure We Are Always Outside Cached
//----------------------------------------
if (!mOutside.Initialized())
{
mOutside.Cache();
}
else
{
// Update All Wind Zones
//-----------------------
if (!mFrozen)
{
mGlobalWindVelocity.Clear();
for (int wz=0; wz