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