/* =========================================================================== 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 // // //////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////// // Includes //////////////////////////////////////////////////////////////////////////////////////// #include "tr_local.h" #include "tr_WorldEffects.h" #include "Ravl/CVec.h" #include "Ratl/vector_vs.h" #include "Ratl/bits_vs.h" #include "glext.h" //////////////////////////////////////////////////////////////////////////////////////// // Defines //////////////////////////////////////////////////////////////////////////////////////// #define GLS_ALPHA (GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA) #define MAX_WIND_ZONES 10 #define MAX_WEATHER_ZONES 10 #define MAX_PUFF_SYSTEMS 2 #define MAX_PARTICLE_CLOUDS 5 #define POINTCACHE_CELL_SIZE 96.0f //////////////////////////////////////////////////////////////////////////////////////// // Globals //////////////////////////////////////////////////////////////////////////////////////// float mMillisecondsElapsed = 0; float mSecondsElapsed = 0; bool mFrozen = false; CVec3 mGlobalWindVelocity; CVec3 mGlobalWindDirection; float mGlobalWindSpeed; int mParticlesRendered; //////////////////////////////////////////////////////////////////////////////////////// // Handy Functions //////////////////////////////////////////////////////////////////////////////////////// // Returns a float min <= x < max (exclusive; will get max - 0.00001; but never max) inline float WE_flrand(float min, float max) { return ((rand() * (max - min)) / (RAND_MAX)) + min; } //////////////////////////////////////////////////////////////////////////////////////// // Externs & Fwd Decl. //////////////////////////////////////////////////////////////////////////////////////// extern void SetViewportAndScissor( void ); 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 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] = WE_flrand(mMins[0], mMaxs[0]); V[1] = WE_flrand(mMins[1], mMaxs[1]); V[2] = WE_flrand(mMins[2], mMaxs[2]); } inline void Wrap(CVec3& V, SVecRange &spawnRange) { if (V[0]mMaxs[0]) { const float d = V[0]-mMaxs[0]; V[0] = mMins[0]+fmod(d, mMaxs[0]-mMins[0]); } if (V[1]mMaxs[1]) { const float d = V[1]-mMaxs[1]; V[1] = mMins[1]+fmod(d, mMaxs[1]-mMins[1]); } if (V[2]mMaxs[2]) { const float d = V[2]-mMaxs[2]; V[2] = mMins[2]+fmod(d, mMaxs[2]-mMins[2]); } } 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; bool R_GetWindVector(vec3_t windVector) { VectorCopy(mGlobalWindDirection.v, windVector); return true; } bool R_GetWindSpeed(float &windSpeed) { windSpeed = mGlobalWindSpeed; return true; } bool R_GetWindGusting() { return (mGlobalWindSpeed>1000.0f); } //////////////////////////////////////////////////////////////////////////////////////// // Outside Point Cache //////////////////////////////////////////////////////////////////////////////////////// class COutside { public: //////////////////////////////////////////////////////////////////////////////////// //Global Public Outside Variables //////////////////////////////////////////////////////////////////////////////////// bool mOutsideShake; float mOutsidePain; private: //////////////////////////////////////////////////////////////////////////////////// // The Outside Cache //////////////////////////////////////////////////////////////////////////////////// bool mCacheInit; // Has It Been Cached? struct SWeatherZone { static bool mMarkedOutside; uint32_t* mPointCache; SVecRange mExtents; SVecRange mSize; int mWidth; int mHeight; int mDepth; //////////////////////////////////////////////////////////////////////////////////// // 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; for (int wz=0; wz> 5; int arraySize = (Wz.mWidth * Wz.mHeight * Wz.mDepth); Wz.mPointCache = (uint32_t *)Z_Malloc(arraySize*sizeof(uint32_t), TAG_POINTCACHE, qtrue); } } //////////////////////////////////////////////////////////////////////////////////// // Cache - Will Scan the World, Creating The Cache //////////////////////////////////////////////////////////////////////////////////// void Cache() { if (!tr.world || mCacheInit) { return; } CVec3 CurPos; CVec3 Size; CVec3 Mins; int x, y, z, q, zbase; bool curPosOutside; uint32_t contents; uint32_t bit; // Record The Extents Of The World Incase No Other Weather Zones Exist //--------------------------------------------------------------------- if (!mWeatherZones.size()) { ri.Printf( PRINT_ALL, "WARNING: No Weather Zones Encountered\n"); AddWeatherZone(tr.world->bmodels[0].bounds[0], tr.world->bmodels[0].bounds[1]); } // Iterate Over All Weather Zones //-------------------------------- for (int zone=0; zoneSRC 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, "CWeatherParticleCloud: Could not texture %s", texturePath); } GL_Bind(mImage); // Create The Particles //---------------------- mParticleCount = count; mParticles = new CWeatherParticle[mParticleCount]; CWeatherParticle* 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 //////////////////////////////////////////////////////////////////////////////////// CWeatherParticleCloud() { mImage = 0; mParticleCount = 0; Reset(); } //////////////////////////////////////////////////////////////////////////////////// // Initialize - Will setup default values for all data //////////////////////////////////////////////////////////////////////////////////// ~CWeatherParticleCloud() { 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() { CWeatherParticle* part=0; CVec3 partForce; CVec3 partMoved; CVec3 partToCamera; bool partRendering; bool partOutside; bool partInRange; bool partInView; int particleNum; float particleFade = (mFade * mSecondsElapsed); /* TODO: Non Global Wind Zones CWindZone* wind=0; int windNum; int windCount = mWindZones.size(); */ // 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); if (mOrientWithVelocity) { mCameraDown = mSpawnPlaneNorm; mCameraDown *= (mHeight * -1); } } // 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; 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(CWeatherParticle::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*WE_flrand(-mSpawnPlaneSize, mSpawnPlaneSize)); part->mPosition += (mSpawnPlaneUp* WE_flrand(-mSpawnPlaneSize, mSpawnPlaneSize)); } // Otherwise, Just Wrap Around To The Other End Of The Range //----------------------------------------------------------- else { mRange.Wrap(part->mPosition, mSpawnRange); } partInRange = true; } // Process Fade //-------------- { // Start A Fade Out //------------------ if (partRendering && !partInView) { part->mFlags.clear_bit(CWeatherParticle::FLAG_FADEIN); part->mFlags.set_bit(CWeatherParticle::FLAG_FADEOUT); } // Switch From Fade Out To Fade In //--------------------------------- else if (partRendering && partInView && part->mFlags.get_bit(CWeatherParticle::FLAG_FADEOUT)) { part->mFlags.set_bit(CWeatherParticle::FLAG_FADEIN); part->mFlags.clear_bit(CWeatherParticle::FLAG_FADEOUT); } // Start A Fade In //----------------- else if (!partRendering && partInView) { partRendering = true; part->mAlpha = 0.0f; part->mFlags.set_bit(CWeatherParticle::FLAG_RENDER); part->mFlags.set_bit(CWeatherParticle::FLAG_FADEIN); part->mFlags.clear_bit(CWeatherParticle::FLAG_FADEOUT); } // Update Fade //------------- if (partRendering) { // Update Fade Out //----------------- if (part->mFlags.get_bit(CWeatherParticle::FLAG_FADEOUT)) { part->mAlpha -= particleFade; if (part->mAlpha<=0.0f) { part->mAlpha = 0.0f; part->mFlags.clear_bit(CWeatherParticle::FLAG_FADEOUT); part->mFlags.clear_bit(CWeatherParticle::FLAG_FADEIN); part->mFlags.clear_bit(CWeatherParticle::FLAG_RENDER); partRendering = false; } } // Update Fade In //---------------- else if (part->mFlags.get_bit(CWeatherParticle::FLAG_FADEIN)) { part->mAlpha += particleFade; if (part->mAlpha>=mColor[3]) { part->mFlags.clear_bit(CWeatherParticle::FLAG_FADEIN); part->mAlpha = mColor[3]; } } } } // Keep Track Of The Number Of Particles To Render //------------------------------------------------- if (part->mFlags.get_bit(CWeatherParticle::FLAG_RENDER)) { mParticleCountRender ++; } } mPopulated = true; } //////////////////////////////////////////////////////////////////////////////////// // Render - //////////////////////////////////////////////////////////////////////////////////// void Render() { CWeatherParticle* part=0; int particleNum; // 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); //naughty, you are making the assumption that culling is on when you get here. -rww GL_Cull(CT_TWO_SIDED); 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(CWeatherParticle::FLAG_RENDER)) { continue; } // 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); //you don't need to do this when you are properly setting cull state. 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) { srand(ri.Milliseconds()); 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