// Copyright (C) 2007 Id Software, Inc. // // This is included by Atmosphere.cpp just to keep it clean... #include "TemplatedParticleSystem.h" #include "Effects.h" #define MAX_PRECIPITATION_PARTICLES 4000 // maximum # of particles //#define MAX_PRECIPITATION_DISTANCE 1000 // maximum distance from refdef origin that particles are visible #define PRECIPITATION_HEIGHT 1000 // maximum distance above the player precipitation will be spawned #define PRECIPITATION_SNOW_HEIGHT 3 // Size of a snow particle #define PRECIPITATION_RAIN_HEIGHT 150 #define PRECIPITATION_DROPDELAY 1000 /************************************************************************/ /* Just adds the parameters field so the atmosphere editor can edit */ /* them more easily */ /************************************************************************/ struct sdPrecipArgs { sdPrecipitationParameters p; const sdHeightMapInstance* heightMap; float currentWaterHeight; idBounds bounds; ID_INLINE float GetGroundHeightAtPos( const idVec3 &pos, const idVec3 &origin ) { return heightMap->GetHeight( pos - origin ); } ID_INLINE float GetPrecipitationDistance( void ) { return p.precipitationDistance; } }; template < class ParticleClass > class sdPrecipitationSystem : public sdTemplatedParticleSystem< ParticleClass, sdPrecipArgs > { public: sdPrecipitationSystem() { this->params.bounds.Clear(); } virtual ~sdPrecipitationSystem( void ) { FreeRenderEntity(); } virtual void PresentRenderEntity( void ); virtual void FreeRenderEntity( void ); static sdPrecipitationSystem* SetupSystem( sdPrecipitationParameters ¶ms, const sdHeightMapInstance* heightMap ) { sdPrecipitationSystem< ParticleClass > *sys = new sdPrecipitationSystem< ParticleClass >(); sys->params.p = params; sys->params.heightMap = heightMap; sys->SetMaxParticles( params.maxParticles ); sys->SetMaterial( params.material ); sys->SetupEffect(); sys->Init(); return sys; } private: void SetupEffect( void ) { renderEffect_t &renderEffect = effect.GetRenderEffect(); renderEffect.declEffect = this->params.p.effect; renderEffect.axis.Identity(); renderEffect.loop = true; renderEffect.shaderParms[SHADERPARM_RED] = 1.0f; renderEffect.shaderParms[SHADERPARM_GREEN] = 1.0f; renderEffect.shaderParms[SHADERPARM_BLUE] = 1.0f; renderEffect.shaderParms[SHADERPARM_ALPHA] = 1.0f; renderEffect.shaderParms[SHADERPARM_BRIGHTNESS] = 1.0f; effectRunning = false; } sdEffect effect; bool effectRunning; }; template < class ParticleClass > void sdPrecipitationSystem::PresentRenderEntity( void ) { sdTemplatedParticleSystem< ParticleClass, sdPrecipArgs >::PresentRenderEntity(); this->params.bounds.ExpandSelf( 5.f ); this->params.currentWaterHeight = -32000.f; const idClipModel* clipModel; int count = gameLocal.clip.ClipModelsTouchingBounds( CLIP_DEBUG_PARMS this->params.bounds, CONTENTS_WATER, &clipModel, 1, NULL ); if ( count ) { if ( clipModel->GetNumCollisionModels() ) { idCollisionModel* model = clipModel->GetCollisionModel( 0 ); int numPlanes = model->GetNumBrushPlanes(); if ( numPlanes ) { this->params.currentWaterHeight = clipModel->GetAbsBounds().GetMaxs()[2]; this->params.bounds.Clear(); } } } if ( !effect.GetRenderEffect().declEffect ) return; // If we are inside don't run the bacground effect int area = gameRenderWorld->PointInArea( this->viewOrg ); bool runEffect = false; if ( area >= 0 ) { if ( gameRenderWorld->GetAreaPortalFlags( area ) & ( 1 << PORTAL_OUTSIDE ) ) { runEffect = true && !g_skipLocalizedPrecipitation.GetBool(); } } // Update the background effect if ( runEffect ) { effect.GetRenderEffect().origin = this->viewOrg; if ( !effectRunning ) { effect.Start( gameLocal.time ); effectRunning = true; } else { effect.Update(); } } else { effect.StopDetach(); effectRunning = false; } } template < class ParticleClass > void sdPrecipitationSystem::FreeRenderEntity( void ) { sdTemplatedParticleSystem< ParticleClass, sdPrecipArgs >::FreeRenderEntity(); if ( !effect.GetRenderEffect().declEffect ) return; effect.FreeRenderEffect(); } /************************************************************************/ /* Snow Flakes */ /************************************************************************/ class sdFlake { private: idVec3 position; idVec3 velocity; idVec3 velocityNorm; float height; float weight; float alpha; int nextDropTime; public: sdFlake( void ) { nextDropTime = -1; //Make yourself inactive } static const int NUM_INSTANCE_VERTEXES = 3; static const int NUM_INSTANCE_INDEXES = 3; void StaticInitializeAndRender( sdTemplatedParticleSystem *sys ) { srfTriangles_t * tri = sys->GetTriSurf(); idDrawVert *verts = tri->verts + tri->numVerts; for ( int i=0; i<3; i++ ) { verts[i].color[0] = 0xFF; verts[i].color[1] = 0xFF; verts[i].color[2] = 0xFF; verts[i].color[3] = 0xFF; switch ( i%3 ) { case 0: verts[i].SetST( 2.0f, 1.0f ); break; case 1: verts[i].SetST( 0.0f, 1.0f ); break; case 2: verts[i].SetST( 0.0f, -1.0f ); break; } } tri->numVerts += 3; } bool Initialize( sdTemplatedParticleSystem *sys ) { float precipDist = sys->params.GetPrecipitationDistance(); position[0] = idRandom::StaticRandom().CRandomFloat() * precipDist; position[1] = idRandom::StaticRandom().CRandomFloat() * precipDist; position[2] = idRandom::StaticRandom().CRandomFloat() * precipDist; position += sys->GetViewOrg(); velocity = sys->params.p.windScale * sdAtmosphere::currentAtmosphere->GetWindVector(); velocity[2] = -(sys->params.p.fallMin + idRandom::StaticRandom().RandomFloat() * ( sys->params.p.fallMax - sys->params.p.fallMin )); float blend = idRandom::StaticRandom().RandomFloat(); // weight and height are related height = sys->params.p.heightMin + blend * ( sys->params.p.heightMax - sys->params.p.heightMin ); weight = sys->params.p.weightMin + blend * ( sys->params.p.weightMax - sys->params.p.weightMin ); // Ensure it doesn't attempt to generate every frame, to prevent // 'clumping' when there's only a small sky area available. nextDropTime = gameLocal.time + PRECIPITATION_DROPDELAY; velocityNorm = velocity; velocityNorm.NormalizeFast(); return true; } bool Update( sdTemplatedParticleSystem *sys ) { idVec2 distance; if( nextDropTime == -1 ) { return false; } position += velocity * (gameLocal.msec * 0.001f); // Snow lives in a box 2*MAX_PRECIPITATION_DISTANCE long and wide and 1.5*MAX_PRECIPITATION_DISTANCE high // This wastes less snow particles in the common case of a player on level ground idVec3 local = position - sys->GetViewOrg(); float precipDist = sys->params.GetPrecipitationDistance(); if ( local.x < -precipDist ) { local.x += 2 * precipDist; } if ( local.y < -precipDist ) { local.y += 2 * precipDist; } if ( local.z < -(precipDist * 0.5) ) { local.z += 1.5 * precipDist; } if ( local.x > precipDist ) { local.x -= 2 * precipDist; } if ( local.y > precipDist ) { local.y -= 2 * precipDist; } if ( local.z > precipDist ) { local.z -= 1.5 * precipDist; } position = local + sys->GetViewOrg(); sys->params.bounds.AddPoint( position ); return true; } void Render( sdTemplatedParticleSystem *sys ) { idVec3 forward, right; idDrawVert* face; idVec2 line; float sinTumbling, cosTumbling, dist; idVec3 start, finish; idVec3 left, up; srfTriangles_t* tri; float size; if ( nextDropTime == -1 ) { return; } start = position; sinTumbling = idMath::Sin( position[2] * 0.03125f * ( 0.5f * weight ) ); cosTumbling = idMath::Cos( ( position[2] + position[1] ) * 0.03125f * ( 0.5f * weight ) ); start[0] += sys->params.p.tumbleStrength * ( 1.0f - velocityNorm[2] ) * sinTumbling; start[1] += sys->params.p.tumbleStrength * ( 1.0f - velocityNorm[2] ) * cosTumbling; if ( start[2] < sys->params.currentWaterHeight ) { return; } if ( start[2] < sys->params.GetGroundHeightAtPos( start, sys->GetRenderEntity().origin ) ) { return; //temporary hide it } line = position.ToVec2() - sys->GetViewOrg().ToVec2(); dist = 1.0f; /*( position - sys->GetViewOrg() ).LengthSqr(); // dist becomes scale if( dist > ( 500.0f * 500.0f ) ) { dist = 1.0f + ( ( dist - ( 500.0f * 500.0f ) ) * ( 10.f / ( 2000.0f * 2000.0f ) ) ); } else { dist = 1.0f; }*/ size = dist * height; tri = sys->GetTriSurf(); face = tri->verts + tri->numVerts; left = sys->GetViewAxis()[1]; up = sys->GetViewAxis()[2]; left *= size; up *= size; face->xyz = start - up - left; face->SetST( 2.0f, 1.0f ); face->color[0] = 0xFF; face->color[1] = 0xFF; face->color[2] = 0xFF; face->color[3] = 0xFF; face++; face->xyz = start - up + left; face->SetST( 0.0f, 1.0f ); face->color[0] = 0xFF; face->color[1] = 0xFF; face->color[2] = 0xFF; face->color[3] = 0xFF; face++; face->xyz = start + up + left; face->SetST( 0.0f, -1.0f ); face->color[0] = 0xFF; face->color[1] = 0xFF; face->color[2] = 0xFF; face->color[3] = 0xFF; face++; tri->indexes[tri->numIndexes + 0] = tri->numVerts + 0; tri->indexes[tri->numIndexes + 1] = tri->numVerts + 1; tri->indexes[tri->numIndexes + 2] = tri->numVerts + 2; tri->numVerts += 3; tri->numIndexes += 3; tri->bounds.AddPoint( position ); } }; /************************************************************************/ /* Rain Drops */ /************************************************************************/ class sdDrop { private: idVec3 position; idVec3 velocity; idVec3 velocityNorm; float height; float weight; float alpha; int nextDropTime; public: sdDrop( void ) { nextDropTime = -1; //Make yourself inactive } static const int NUM_INSTANCE_VERTEXES = 3; static const int NUM_INSTANCE_INDEXES = 3; void StaticInitializeAndRender( sdTemplatedParticleSystem *sys ) { srfTriangles_t * tri = sys->GetTriSurf(); idDrawVert *verts = tri->verts + tri->numVerts; for ( int i=0; i<3; i++ ) { verts[i].color[0] = 0xFF; verts[i].color[1] = 0xFF; verts[i].color[2] = 0xFF; verts[i].color[3] = 0xFF; switch ( i%3 ) { case 0: verts[i].SetST( 2.0f, 1.0f ); break; case 1: verts[i].SetST( 0.0f, 1.0f ); break; case 2: verts[i].SetST( 0.0f, -1.0f ); break; } } tri->numVerts += 3; } bool Initialize( sdTemplatedParticleSystem *sys ) { float precipDist = sys->params.GetPrecipitationDistance(); position[0] = idRandom::StaticRandom().CRandomFloat() * precipDist; position[1] = idRandom::StaticRandom().CRandomFloat() * precipDist; position[2] = idRandom::StaticRandom().CRandomFloat() * precipDist; position += sys->GetViewOrg(); float lower = Max( sys->params.GetGroundHeightAtPos( position, sys->GetRenderEntity().origin ), sys->params.currentWaterHeight ); if ( position[2] < lower ) { float ceil = sys->GetViewOrg()[0] + precipDist; if ( ceil < lower ) { // Very deep underground no point to spawn anything return false; } position[2] = lower + idRandom::StaticRandom().RandomFloat() * ( ceil - lower ); } velocity = sdAtmosphere::currentAtmosphere->GetWindVector() * sys->params.p.windScale; velocity[2] = -(sys->params.p.fallMin + idRandom::StaticRandom().RandomFloat() * ( sys->params.p.fallMax - sys->params.p.fallMin )); float blend = idRandom::StaticRandom().RandomFloat(); // weight and height are related height = sys->params.p.heightMin + blend * ( sys->params.p.heightMax - sys->params.p.heightMin ); weight = sys->params.p.weightMin + blend * ( sys->params.p.weightMax - sys->params.p.weightMin ); // Ensure it doesn't attempt to generate every frame, to prevent // 'clumping' when there's only a small sky area available. nextDropTime = gameLocal.time + PRECIPITATION_DROPDELAY; velocityNorm = velocity; velocityNorm.NormalizeFast(); return true; } bool Update( sdTemplatedParticleSystem *sys ) { idVec2 distance; if( nextDropTime == -1 ) { return false; } position += velocity * (gameLocal.msec * 0.001f); idVec3 local = position - sys->GetViewOrg(); float precipDist = sys->params.GetPrecipitationDistance(); if ( local.x < -precipDist ) { local.x += 2 * precipDist; } if ( local.y < -precipDist ) { local.y += 2 * precipDist; } if ( local.z < -precipDist ) { //local.z += 2 * MAX_PRECIPITATION_DISTANCE; nextDropTime = -1; return false; } if ( local.x > precipDist ) { local.x -= 2 * precipDist; } if ( local.y > precipDist ) { local.y -= 2 * precipDist; } if ( local.z > precipDist ) { //local.z -= 2 * MAX_PRECIPITATION_DISTANCE; nextDropTime = -1; return false; } position = local + sys->GetViewOrg(); if ( position[2] < sys->params.currentWaterHeight ) { nextDropTime = -1; return false; } // If it moved underground kill it if( position[2] < sys->params.GetGroundHeightAtPos( position, sys->GetRenderEntity().origin ) ) { nextDropTime = -1; return false; } sys->params.bounds.AddPoint( position ); return true; } void Render( sdTemplatedParticleSystem *sys ) { // Draw a raindrop idVec3 forward, right; idDrawVert *face; srfTriangles_t *tri; idVec2 line; float len, dist; idVec3 start, finish; if( nextDropTime == -1 ) { return; } start = position; dist = ( position - sys->GetViewOrg() ).LengthSqr(); // Make sure it doesn't clip through surfaces len = height; // fade nearby rain particles if( dist < ( 128.f * 128.f ) ) { dist = .25f + .75f * ( dist / ( 128.f * 128.f ) ); } else { dist = 1.0f; } forward = velocityNorm; finish = start - forward * len; line[0] = forward * sys->GetViewAxis()[1]; line[1] = forward * sys->GetViewAxis()[2]; right = sys->GetViewAxis()[1] * line[1] + sys->GetViewAxis()[2] * -line[0]; right.Normalize(); // dist = 1.0; tri = sys->GetTriSurf(); face = tri->verts + tri->numVerts; face->xyz = finish; face->SetST( 2.0f, 1.0f ); face->color[0] = 0xFF; face->color[1] = 0xFF; face->color[2] = 0xFF; face->color[3] = 0xFF; face++; face->xyz = start + weight * right; face->SetST( 0.0f, 1.0f ); face->color[0] = 0xFF; face->color[1] = 0xFF; face->color[2] = 0xFF; face->color[3] = 0xFF; face++; face->xyz = start - weight * right; face->SetST( 0.0f, -1.0f ); face->color[0] = 0xFF; face->color[1] = 0xFF; face->color[2] = 0xFF; face->color[3] = 0xFF; face++; tri->indexes[tri->numIndexes + 0] = tri->numVerts + 0; tri->indexes[tri->numIndexes + 1] = tri->numVerts + 1; tri->indexes[tri->numIndexes + 2] = tri->numVerts + 2; tri->numVerts += 3; tri->numIndexes += 3; tri->bounds.AddPoint( position ); } }; /************************************************************************/ /* Rain Splashes */ /************************************************************************/ class sdSplash { private: idVec3 position; idVec3 velocity; float size; float weight; float alpha; int lifeTime; int dropTime; public: sdSplash( void ) { dropTime = -1; //Make yourself inactive } static const int NUM_INSTANCE_VERTEXES = 4; static const int NUM_INSTANCE_INDEXES = 6; void StaticInitializeAndRender( sdTemplatedParticleSystem *sys ) { srfTriangles_t * tri = sys->GetTriSurf(); idDrawVert *verts = tri->verts + tri->numVerts; for ( int i=0; i<4; i++ ) { verts[i].color[0] = 0xFF; verts[i].color[1] = 0xFF; verts[i].color[2] = 0xFF; verts[i].color[3] = 0xFF; switch ( i%4 ) { case 0: verts[i].SetST( 1.0f, 1.0f ); break; case 1: verts[i].SetST( 0.0f, 1.0f ); break; case 2: verts[i].SetST( 0.0f, 0.0f ); break; case 3: verts[i].SetST( 1.0f, 0.0f ); break; } } tri->numVerts += 4; } bool Initialize( sdTemplatedParticleSystem *sys ) { float precipDist = sys->params.GetPrecipitationDistance(); position.x = idRandom::StaticRandom().CRandomFloat() * precipDist; position.y = idRandom::StaticRandom().CRandomFloat() * precipDist; position += sys->GetViewOrg(); position.z = sys->params.GetGroundHeightAtPos( position, sys->GetRenderEntity().origin ); if ( position.z < sys->params.currentWaterHeight ) { position.z = sys->params.currentWaterHeight; } velocity = sys->params.p.windScale * sdAtmosphere::currentAtmosphere->GetWindVector(); velocity.z = (sys->params.p.fallMin + idRandom::StaticRandom().RandomFloat() * ( sys->params.p.fallMax - sys->params.p.fallMin )); float blend = idRandom::StaticRandom().RandomFloat(); // weight and height are related size = sys->params.p.heightMin + blend * ( sys->params.p.heightMax - sys->params.p.heightMin ); weight = sys->params.p.weightMin + blend * ( sys->params.p.weightMax - sys->params.p.weightMin ); position.z += sys->params.p.tumbleStrength; // Ensure it doesn't attempt to generate every frame, to prevent // 'clumping' when there's only a small sky area available. lifeTime = sys->params.p.timeMin + idRandom::StaticRandom().RandomFloat() * ( sys->params.p.timeMax - sys->params.p.timeMin ); dropTime = /*gameLocal.time + */lifeTime; return true; } bool Update( sdTemplatedParticleSystem *sys ) { idVec2 distance; if ( dropTime < 0 ) { return false; } dropTime -= gameLocal.msec; position += velocity * (gameLocal.msec * 0.001f); size += weight * (gameLocal.msec * 0.001f); // Snow lives in a box 2*MAX_PRECIPITATION_DISTANCE long and wide and 1.5*MAX_PRECIPITATION_DISTANCE high // This wastes less snow particles in the common case of a player on level ground idVec3 local = position - sys->GetViewOrg(); bool wrapped = false; float precipDist = sys->params.GetPrecipitationDistance(); if ( local.x < -precipDist ) { local.x += 2 * precipDist; wrapped = true; } if ( local.y < -precipDist ) { local.y += 2 * precipDist; wrapped = true; } if ( local.x > precipDist ) { local.x -= 2 * precipDist; wrapped = true; } if ( local.y > precipDist ) { local.y -= 2 * precipDist; wrapped = true; } position = local + sys->GetViewOrg(); if ( wrapped ) { position.z = sys->params.GetGroundHeightAtPos( position, sys->GetRenderEntity().origin ) + sys->params.p.tumbleStrength; if ( position.z < (sys->params.currentWaterHeight + sys->params.p.tumbleStrength ) ) { position.z = sys->params.currentWaterHeight + sys->params.p.tumbleStrength; } } sys->params.bounds.AddPoint( position ); return true; } void Render( sdTemplatedParticleSystem *sys ) { idDrawVert* face; idVec3 start; srfTriangles_t* tri; idVec3 left, up; if ( dropTime < 0 ) { return; } start = position; tri = sys->GetTriSurf(); face = tri->verts + tri->numVerts; left = sys->GetViewAxis()[1]; up = sys->GetViewAxis()[2]; left *= size; up *= size; byte color = (int)(((dropTime) / (float)lifeTime) * 255.0f); dword colorDW = color | color << 8 | color << 16 | color << 24; face->xyz = start - up - left; face->SetST( 1.f, 1.f ); face->SetColor( colorDW ); face++; face->xyz = start - up + left; face->SetST( 0.f, 1.f ); face->SetColor( colorDW ); face++; face->xyz = start + up + left; face->SetST( 0.f, 0.f ); face->SetColor( colorDW ); face++; face->xyz = start + up - left; face->SetST( 1.f, 0.f ); face->SetColor( colorDW ); face++; tri->indexes[tri->numIndexes + 0] = tri->numVerts + 0; tri->indexes[tri->numIndexes + 1] = tri->numVerts + 1; tri->indexes[tri->numIndexes + 2] = tri->numVerts + 2; tri->indexes[tri->numIndexes + 3] = tri->numVerts + 0; tri->indexes[tri->numIndexes + 4] = tri->numVerts + 2; tri->indexes[tri->numIndexes + 5] = tri->numVerts + 3; tri->numVerts += 4; tri->numIndexes += 6; tri->bounds.AddPoint( position ); } };