/* =========================================================================== Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). Doom 3 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 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 Source Code. If not, see . In addition, the Doom 3 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 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 "sys/platform.h" #include "renderer/ModelManager.h" #include "Entity.h" #include "Game_local.h" #include "SmokeParticles.h" static const char *smokeParticle_SnapshotName = "_SmokeParticle_Snapshot_"; /* ================ idSmokeParticles::idSmokeParticles ================ */ idSmokeParticles::idSmokeParticles( void ) { 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( void ) { 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( void ) { // 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( void ) { 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; #ifdef _D3XP 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 ); } #else float frac = (float)( gameLocal.time - smoke->privateStartTime ) / ( stage->particleLife * 1000 ); #endif 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; #ifdef _D3XP SetTimeState ts( timeGroup ); #endif if ( !smoke ) { return false; } if ( !gameLocal.isNewFrame ) { return false; } // dedicated doesn't smoke. No UpdateRenderEntity, so they would not be freed if ( gameLocal.localClientNum < 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.msec /*_D3XP - FIX - was USERCMD_MSEC*/ ) / 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 ; 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++; #ifdef _D3XP newSmoke->timeGroup = timeGroup; #endif 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 ) { // FIXME: re-use model surfaces renderEntity->hModel->InitEmpty( smokeParticle_SnapshotName ); // 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 ) { return false; } // don't regenerate it if it is current if ( renderView->time == currentParticleTime && !renderView->forceUpdate ) { return false; } currentParticleTime = renderView->time; 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; #ifdef _D3XP 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); } #else g.frac = (float)( gameLocal.time - smoke->privateStartTime ) / (stage->particleLife * 1000); #endif 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; }