/* =========================================================================== Doom 3 BFG Edition GPL Source Code Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see . In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include "../idlib/precompiled.h" #pragma hdrstop #include "Game_local.h" static const char* smokeParticle_SnapshotName = "_SmokeParticle_Snapshot_"; /* ================ idSmokeParticles::idSmokeParticles ================ */ idSmokeParticles::idSmokeParticles() { initialized = false; memset( &renderEntity, 0, sizeof( renderEntity ) ); renderEntityHandle = -1; memset( smokes, 0, sizeof( smokes ) ); freeSmokes = NULL; numActiveSmokes = 0; currentParticleTime = -1; } /* ================ idSmokeParticles::Init ================ */ void idSmokeParticles::Init() { if( initialized ) { Shutdown(); } // set up the free list for( int i = 0; i < MAX_SMOKE_PARTICLES - 1; i++ ) { smokes[i].next = &smokes[i + 1]; } smokes[MAX_SMOKE_PARTICLES - 1].next = NULL; freeSmokes = &smokes[0]; numActiveSmokes = 0; activeStages.Clear(); memset( &renderEntity, 0, sizeof( renderEntity ) ); renderEntity.bounds.Clear(); renderEntity.axis = mat3_identity; renderEntity.shaderParms[ SHADERPARM_RED ] = 1; renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1; renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1; renderEntity.shaderParms[3] = 1; renderEntity.hModel = renderModelManager->AllocModel(); renderEntity.hModel->InitEmpty( smokeParticle_SnapshotName ); // we certainly don't want particle shadows renderEntity.noShadow = 1; // huge bounds, so it will be present in every world area renderEntity.bounds.AddPoint( idVec3( -100000, -100000, -100000 ) ); renderEntity.bounds.AddPoint( idVec3( 100000, 100000, 100000 ) ); renderEntity.callback = idSmokeParticles::ModelCallback; // add to renderer list renderEntityHandle = gameRenderWorld->AddEntityDef( &renderEntity ); currentParticleTime = -1; initialized = true; } /* ================ idSmokeParticles::Shutdown ================ */ void idSmokeParticles::Shutdown() { // make sure the render entity is freed before the model is freed if( renderEntityHandle != -1 ) { gameRenderWorld->FreeEntityDef( renderEntityHandle ); renderEntityHandle = -1; } if( renderEntity.hModel != NULL ) { renderModelManager->FreeModel( renderEntity.hModel ); renderEntity.hModel = NULL; } initialized = false; } /* ================ idSmokeParticles::FreeSmokes ================ */ void idSmokeParticles::FreeSmokes() { for( int activeStageNum = 0; activeStageNum < activeStages.Num(); activeStageNum++ ) { singleSmoke_t* smoke, *next, *last; activeSmokeStage_t* active = &activeStages[activeStageNum]; const idParticleStage* stage = active->stage; for( last = NULL, smoke = active->smokes; smoke; smoke = next ) { next = smoke->next; float frac; if( smoke->timeGroup ) { frac = ( float )( gameLocal.fast.time - smoke->privateStartTime ) / ( stage->particleLife * 1000 ); } else { frac = ( float )( gameLocal.slow.time - smoke->privateStartTime ) / ( stage->particleLife * 1000 ); } if( frac >= 1.0f ) { // remove the particle from the stage list if( last != NULL ) { last->next = smoke->next; } else { active->smokes = smoke->next; } // put the particle on the free list smoke->next = freeSmokes; freeSmokes = smoke; numActiveSmokes--; continue; } last = smoke; } if( !active->smokes ) { // remove this from the activeStages list activeStages.RemoveIndex( activeStageNum ); activeStageNum--; } } } /* ================ idSmokeParticles::EmitSmoke Called by game code to drop another particle into the list ================ */ bool idSmokeParticles::EmitSmoke( const idDeclParticle* smoke, const int systemStartTime, const float diversity, const idVec3& origin, const idMat3& axis, int timeGroup /*_D3XP*/ ) { bool continues = false; SetTimeState ts( timeGroup ); if( !smoke ) { return false; } if( !gameLocal.isNewFrame ) { return false; } // dedicated doesn't smoke. No UpdateRenderEntity, so they would not be freed if( gameLocal.GetLocalClientNum() < 0 ) { return false; } assert( gameLocal.time == 0 || systemStartTime <= gameLocal.time ); if( systemStartTime > gameLocal.time ) { return false; } idRandom steppingRandom( 0xffff * diversity ); // for each stage in the smoke that is still emitting particles, emit a new singleSmoke_t for( int stageNum = 0; stageNum < smoke->stages.Num(); stageNum++ ) { const idParticleStage* stage = smoke->stages[stageNum]; if( !stage->cycleMsec ) { continue; } if( !stage->material ) { continue; } if( stage->particleLife <= 0 ) { continue; } // see how many particles we should emit this tic // FIXME: smoke.privateStartTime += stage->timeOffset; int finalParticleTime = stage->cycleMsec * stage->spawnBunching; int deltaMsec = gameLocal.time - systemStartTime; int nowCount = 0, prevCount = 0; if( finalParticleTime == 0 ) { // if spawnBunching is 0, they will all come out at once if( gameLocal.time == systemStartTime ) { prevCount = -1; nowCount = stage->totalParticles - 1; } else { prevCount = stage->totalParticles; } } else { nowCount = floor( ( ( float )deltaMsec / finalParticleTime ) * stage->totalParticles ); if( nowCount >= stage->totalParticles ) { nowCount = stage->totalParticles - 1; } prevCount = floor( ( ( float )( deltaMsec - ( gameLocal.time - gameLocal.previousTime ) ) / finalParticleTime ) * stage->totalParticles ); if( prevCount < -1 ) { prevCount = -1; } } if( prevCount >= stage->totalParticles ) { // no more particles from this stage continue; } if( nowCount < stage->totalParticles - 1 ) { // the system will need to emit particles next frame as well continues = true; } // find an activeSmokeStage that matches this activeSmokeStage_t* active = NULL; int i; for( i = 0 ; i < activeStages.Num() ; i++ ) { active = &activeStages[i]; if( active->stage == stage ) { break; } } if( i == activeStages.Num() ) { // add a new one activeSmokeStage_t newActive; newActive.smokes = NULL; newActive.stage = stage; i = activeStages.Append( newActive ); active = &activeStages[i]; } // add all the required particles for( prevCount++ ; prevCount <= nowCount && active != NULL ; prevCount++ ) { if( !freeSmokes ) { gameLocal.Printf( "idSmokeParticles::EmitSmoke: no free smokes with %d active stages\n", activeStages.Num() ); return true; } singleSmoke_t* newSmoke = freeSmokes; freeSmokes = freeSmokes->next; numActiveSmokes++; newSmoke->timeGroup = timeGroup; newSmoke->index = prevCount; newSmoke->axis = axis; newSmoke->origin = origin; newSmoke->random = steppingRandom; newSmoke->privateStartTime = systemStartTime + prevCount * finalParticleTime / stage->totalParticles; newSmoke->next = active->smokes; active->smokes = newSmoke; steppingRandom.RandomInt(); // advance the random } } return continues; } /* ================ idSmokeParticles::UpdateRenderEntity ================ */ bool idSmokeParticles::UpdateRenderEntity( renderEntity_s* renderEntity, const renderView_t* renderView ) { // this may be triggered by a model trace or other non-view related source, // to which we should look like an empty model if( !renderView ) { // FIXME: re-use model surfaces renderEntity->hModel->InitEmpty( smokeParticle_SnapshotName ); return false; } // don't regenerate it if it is current if( renderView->time[renderEntity->timeGroup] == currentParticleTime && !renderView->forceUpdate ) { return false; } // FIXME: re-use model surfaces renderEntity->hModel->InitEmpty( smokeParticle_SnapshotName ); currentParticleTime = renderView->time[renderEntity->timeGroup]; particleGen_t g; g.renderEnt = renderEntity; g.renderView = renderView; for( int activeStageNum = 0; activeStageNum < activeStages.Num(); activeStageNum++ ) { singleSmoke_t* smoke, *next, *last; activeSmokeStage_t* active = &activeStages[activeStageNum]; const idParticleStage* stage = active->stage; if( !stage->material ) { continue; } // allocate a srfTriangles that can hold all the particles int count = 0; for( smoke = active->smokes; smoke; smoke = smoke->next ) { count++; } int quads = count * stage->NumQuadsPerParticle(); srfTriangles_t* tri = renderEntity->hModel->AllocSurfaceTriangles( quads * 4, quads * 6 ); tri->numIndexes = quads * 6; tri->numVerts = quads * 4; // just always draw the particles tri->bounds[0][0] = tri->bounds[0][1] = tri->bounds[0][2] = -99999; tri->bounds[1][0] = tri->bounds[1][1] = tri->bounds[1][2] = 99999; tri->numVerts = 0; for( last = NULL, smoke = active->smokes; smoke; smoke = next ) { next = smoke->next; if( smoke->timeGroup ) { g.frac = ( float )( gameLocal.fast.time - smoke->privateStartTime ) / ( stage->particleLife * 1000 ); } else { g.frac = ( float )( gameLocal.time - smoke->privateStartTime ) / ( stage->particleLife * 1000 ); } if( g.frac >= 1.0f ) { // remove the particle from the stage list if( last != NULL ) { last->next = smoke->next; } else { active->smokes = smoke->next; } // put the particle on the free list smoke->next = freeSmokes; freeSmokes = smoke; numActiveSmokes--; continue; } g.index = smoke->index; g.random = smoke->random; g.origin = smoke->origin; g.axis = smoke->axis; g.originalRandom = g.random; g.age = g.frac * stage->particleLife; tri->numVerts += stage->CreateParticle( &g, tri->verts + tri->numVerts ); last = smoke; } if( tri->numVerts > quads * 4 ) { gameLocal.Error( "idSmokeParticles::UpdateRenderEntity: miscounted verts" ); } if( tri->numVerts == 0 ) { // they were all removed renderEntity->hModel->FreeSurfaceTriangles( tri ); if( !active->smokes ) { // remove this from the activeStages list activeStages.RemoveIndex( activeStageNum ); activeStageNum--; } } else { // build the index list int indexes = 0; for( int i = 0 ; i < tri->numVerts ; i += 4 ) { tri->indexes[indexes + 0] = i; tri->indexes[indexes + 1] = i + 2; tri->indexes[indexes + 2] = i + 3; tri->indexes[indexes + 3] = i; tri->indexes[indexes + 4] = i + 3; tri->indexes[indexes + 5] = i + 1; indexes += 6; } tri->numIndexes = indexes; modelSurface_t surf; surf.geometry = tri; surf.shader = stage->material; surf.id = 0; renderEntity->hModel->AddSurface( surf ); } } return true; } /* ================ idSmokeParticles::ModelCallback ================ */ bool idSmokeParticles::ModelCallback( renderEntity_s* renderEntity, const renderView_t* renderView ) { // update the particles if( gameLocal.smokeParticles ) { return gameLocal.smokeParticles->UpdateRenderEntity( renderEntity, renderView ); } return true; }