SmokeParticles.cpp (12448B)
1 /* 2 =========================================================================== 3 4 Doom 3 BFG Edition GPL Source Code 5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 6 7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). 8 9 Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify 10 it under the terms of the GNU General Public License as published by 11 the Free Software Foundation, either version 3 of the License, or 12 (at your option) any later version. 13 14 Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, 15 but WITHOUT ANY WARRANTY; without even the implied warranty of 16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 GNU General Public License for more details. 18 19 You should have received a copy of the GNU General Public License 20 along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>. 21 22 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. 23 24 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. 25 26 =========================================================================== 27 */ 28 29 #include "../idlib/precompiled.h" 30 #pragma hdrstop 31 32 #include "Game_local.h" 33 34 static const char *smokeParticle_SnapshotName = "_SmokeParticle_Snapshot_"; 35 36 /* 37 ================ 38 idSmokeParticles::idSmokeParticles 39 ================ 40 */ 41 idSmokeParticles::idSmokeParticles() { 42 initialized = false; 43 memset( &renderEntity, 0, sizeof( renderEntity ) ); 44 renderEntityHandle = -1; 45 memset( smokes, 0, sizeof( smokes ) ); 46 freeSmokes = NULL; 47 numActiveSmokes = 0; 48 currentParticleTime = -1; 49 } 50 51 /* 52 ================ 53 idSmokeParticles::Init 54 ================ 55 */ 56 void idSmokeParticles::Init() { 57 if ( initialized ) { 58 Shutdown(); 59 } 60 61 // set up the free list 62 for ( int i = 0; i < MAX_SMOKE_PARTICLES-1; i++ ) { 63 smokes[i].next = &smokes[i+1]; 64 } 65 smokes[MAX_SMOKE_PARTICLES-1].next = NULL; 66 freeSmokes = &smokes[0]; 67 numActiveSmokes = 0; 68 69 activeStages.Clear(); 70 71 memset( &renderEntity, 0, sizeof( renderEntity ) ); 72 73 renderEntity.bounds.Clear(); 74 renderEntity.axis = mat3_identity; 75 renderEntity.shaderParms[ SHADERPARM_RED ] = 1; 76 renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1; 77 renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1; 78 renderEntity.shaderParms[3] = 1; 79 80 renderEntity.hModel = renderModelManager->AllocModel(); 81 renderEntity.hModel->InitEmpty( smokeParticle_SnapshotName ); 82 83 // we certainly don't want particle shadows 84 renderEntity.noShadow = 1; 85 86 // huge bounds, so it will be present in every world area 87 renderEntity.bounds.AddPoint( idVec3(-100000, -100000, -100000) ); 88 renderEntity.bounds.AddPoint( idVec3( 100000, 100000, 100000) ); 89 90 renderEntity.callback = idSmokeParticles::ModelCallback; 91 // add to renderer list 92 renderEntityHandle = gameRenderWorld->AddEntityDef( &renderEntity ); 93 94 currentParticleTime = -1; 95 96 initialized = true; 97 } 98 99 /* 100 ================ 101 idSmokeParticles::Shutdown 102 ================ 103 */ 104 void idSmokeParticles::Shutdown() { 105 // make sure the render entity is freed before the model is freed 106 if ( renderEntityHandle != -1 ) { 107 gameRenderWorld->FreeEntityDef( renderEntityHandle ); 108 renderEntityHandle = -1; 109 } 110 if ( renderEntity.hModel != NULL ) { 111 renderModelManager->FreeModel( renderEntity.hModel ); 112 renderEntity.hModel = NULL; 113 } 114 initialized = false; 115 } 116 117 /* 118 ================ 119 idSmokeParticles::FreeSmokes 120 ================ 121 */ 122 void idSmokeParticles::FreeSmokes() { 123 for ( int activeStageNum = 0; activeStageNum < activeStages.Num(); activeStageNum++ ) { 124 singleSmoke_t *smoke, *next, *last; 125 126 activeSmokeStage_t *active = &activeStages[activeStageNum]; 127 const idParticleStage *stage = active->stage; 128 129 for ( last = NULL, smoke = active->smokes; smoke; smoke = next ) { 130 next = smoke->next; 131 132 float frac; 133 134 if ( smoke->timeGroup ) { 135 frac = (float)( gameLocal.fast.time - smoke->privateStartTime ) / ( stage->particleLife * 1000 ); 136 } 137 else { 138 frac = (float)( gameLocal.slow.time - smoke->privateStartTime ) / ( stage->particleLife * 1000 ); 139 } 140 if ( frac >= 1.0f ) { 141 // remove the particle from the stage list 142 if ( last != NULL ) { 143 last->next = smoke->next; 144 } else { 145 active->smokes = smoke->next; 146 } 147 // put the particle on the free list 148 smoke->next = freeSmokes; 149 freeSmokes = smoke; 150 numActiveSmokes--; 151 continue; 152 } 153 154 last = smoke; 155 } 156 157 if ( !active->smokes ) { 158 // remove this from the activeStages list 159 activeStages.RemoveIndex( activeStageNum ); 160 activeStageNum--; 161 } 162 } 163 } 164 165 /* 166 ================ 167 idSmokeParticles::EmitSmoke 168 169 Called by game code to drop another particle into the list 170 ================ 171 */ 172 bool idSmokeParticles::EmitSmoke( const idDeclParticle *smoke, const int systemStartTime, const float diversity, const idVec3 &origin, const idMat3 &axis, int timeGroup /*_D3XP*/ ) { 173 bool continues = false; 174 SetTimeState ts( timeGroup ); 175 176 if ( !smoke ) { 177 return false; 178 } 179 180 if ( !gameLocal.isNewFrame ) { 181 return false; 182 } 183 184 // dedicated doesn't smoke. No UpdateRenderEntity, so they would not be freed 185 if ( gameLocal.GetLocalClientNum() < 0 ) { 186 return false; 187 } 188 189 assert( gameLocal.time == 0 || systemStartTime <= gameLocal.time ); 190 if ( systemStartTime > gameLocal.time ) { 191 return false; 192 } 193 194 idRandom steppingRandom( 0xffff * diversity ); 195 196 // for each stage in the smoke that is still emitting particles, emit a new singleSmoke_t 197 for ( int stageNum = 0; stageNum < smoke->stages.Num(); stageNum++ ) { 198 const idParticleStage *stage = smoke->stages[stageNum]; 199 200 if ( !stage->cycleMsec ) { 201 continue; 202 } 203 204 if ( !stage->material ) { 205 continue; 206 } 207 208 if ( stage->particleLife <= 0 ) { 209 continue; 210 } 211 212 // see how many particles we should emit this tic 213 // FIXME: smoke.privateStartTime += stage->timeOffset; 214 int finalParticleTime = stage->cycleMsec * stage->spawnBunching; 215 int deltaMsec = gameLocal.time - systemStartTime; 216 217 int nowCount = 0, prevCount = 0; 218 if ( finalParticleTime == 0 ) { 219 // if spawnBunching is 0, they will all come out at once 220 if ( gameLocal.time == systemStartTime ) { 221 prevCount = -1; 222 nowCount = stage->totalParticles-1; 223 } else { 224 prevCount = stage->totalParticles; 225 } 226 } else { 227 nowCount = floor( ( (float)deltaMsec / finalParticleTime ) * stage->totalParticles ); 228 if ( nowCount >= stage->totalParticles ) { 229 nowCount = stage->totalParticles-1; 230 } 231 prevCount = floor( ((float)( deltaMsec - ( gameLocal.time - gameLocal.previousTime ) ) / finalParticleTime) * stage->totalParticles ); 232 if ( prevCount < -1 ) { 233 prevCount = -1; 234 } 235 } 236 237 if ( prevCount >= stage->totalParticles ) { 238 // no more particles from this stage 239 continue; 240 } 241 242 if ( nowCount < stage->totalParticles-1 ) { 243 // the system will need to emit particles next frame as well 244 continues = true; 245 } 246 247 // find an activeSmokeStage that matches this 248 activeSmokeStage_t *active = NULL; 249 int i; 250 for ( i = 0 ; i < activeStages.Num() ; i++ ) { 251 active = &activeStages[i]; 252 if ( active->stage == stage ) { 253 break; 254 } 255 } 256 if ( i == activeStages.Num() ) { 257 // add a new one 258 activeSmokeStage_t newActive; 259 260 newActive.smokes = NULL; 261 newActive.stage = stage; 262 i = activeStages.Append( newActive ); 263 active = &activeStages[i]; 264 } 265 266 // add all the required particles 267 for ( prevCount++ ; prevCount <= nowCount && active != NULL ; prevCount++ ) { 268 if ( !freeSmokes ) { 269 gameLocal.Printf( "idSmokeParticles::EmitSmoke: no free smokes with %d active stages\n", activeStages.Num() ); 270 return true; 271 } 272 singleSmoke_t *newSmoke = freeSmokes; 273 freeSmokes = freeSmokes->next; 274 numActiveSmokes++; 275 276 newSmoke->timeGroup = timeGroup; 277 newSmoke->index = prevCount; 278 newSmoke->axis = axis; 279 newSmoke->origin = origin; 280 newSmoke->random = steppingRandom; 281 newSmoke->privateStartTime = systemStartTime + prevCount * finalParticleTime / stage->totalParticles; 282 newSmoke->next = active->smokes; 283 active->smokes = newSmoke; 284 285 steppingRandom.RandomInt(); // advance the random 286 } 287 } 288 289 return continues; 290 } 291 292 /* 293 ================ 294 idSmokeParticles::UpdateRenderEntity 295 ================ 296 */ 297 bool idSmokeParticles::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) { 298 299 // this may be triggered by a model trace or other non-view related source, 300 // to which we should look like an empty model 301 if ( !renderView ) { 302 // FIXME: re-use model surfaces 303 renderEntity->hModel->InitEmpty( smokeParticle_SnapshotName ); 304 return false; 305 } 306 307 // don't regenerate it if it is current 308 if ( renderView->time[renderEntity->timeGroup] == currentParticleTime && !renderView->forceUpdate ) { 309 return false; 310 } 311 312 // FIXME: re-use model surfaces 313 renderEntity->hModel->InitEmpty( smokeParticle_SnapshotName ); 314 315 currentParticleTime = renderView->time[renderEntity->timeGroup]; 316 317 particleGen_t g; 318 319 g.renderEnt = renderEntity; 320 g.renderView = renderView; 321 322 for ( int activeStageNum = 0; activeStageNum < activeStages.Num(); activeStageNum++ ) { 323 singleSmoke_t *smoke, *next, *last; 324 325 activeSmokeStage_t *active = &activeStages[activeStageNum]; 326 const idParticleStage *stage = active->stage; 327 328 if ( !stage->material ) { 329 continue; 330 } 331 332 // allocate a srfTriangles that can hold all the particles 333 int count = 0; 334 for ( smoke = active->smokes; smoke; smoke = smoke->next ) { 335 count++; 336 } 337 int quads = count * stage->NumQuadsPerParticle(); 338 srfTriangles_t *tri = renderEntity->hModel->AllocSurfaceTriangles( quads * 4, quads * 6 ); 339 tri->numIndexes = quads * 6; 340 tri->numVerts = quads * 4; 341 342 // just always draw the particles 343 tri->bounds[0][0] = 344 tri->bounds[0][1] = 345 tri->bounds[0][2] = -99999; 346 tri->bounds[1][0] = 347 tri->bounds[1][1] = 348 tri->bounds[1][2] = 99999; 349 350 tri->numVerts = 0; 351 for ( last = NULL, smoke = active->smokes; smoke; smoke = next ) { 352 next = smoke->next; 353 354 if ( smoke->timeGroup ) { 355 g.frac = (float)( gameLocal.fast.time - smoke->privateStartTime ) / (stage->particleLife * 1000); 356 } 357 else { 358 g.frac = (float)( gameLocal.time - smoke->privateStartTime ) / (stage->particleLife * 1000); 359 } 360 if ( g.frac >= 1.0f ) { 361 // remove the particle from the stage list 362 if ( last != NULL ) { 363 last->next = smoke->next; 364 } else { 365 active->smokes = smoke->next; 366 } 367 // put the particle on the free list 368 smoke->next = freeSmokes; 369 freeSmokes = smoke; 370 numActiveSmokes--; 371 continue; 372 } 373 374 g.index = smoke->index; 375 g.random = smoke->random; 376 377 g.origin = smoke->origin; 378 g.axis = smoke->axis; 379 380 g.originalRandom = g.random; 381 g.age = g.frac * stage->particleLife; 382 383 tri->numVerts += stage->CreateParticle( &g, tri->verts + tri->numVerts ); 384 385 last = smoke; 386 } 387 if ( tri->numVerts > quads * 4 ) { 388 gameLocal.Error( "idSmokeParticles::UpdateRenderEntity: miscounted verts" ); 389 } 390 391 if ( tri->numVerts == 0 ) { 392 393 // they were all removed 394 renderEntity->hModel->FreeSurfaceTriangles( tri ); 395 396 if ( !active->smokes ) { 397 // remove this from the activeStages list 398 activeStages.RemoveIndex( activeStageNum ); 399 activeStageNum--; 400 } 401 } else { 402 // build the index list 403 int indexes = 0; 404 for ( int i = 0 ; i < tri->numVerts ; i += 4 ) { 405 tri->indexes[indexes+0] = i; 406 tri->indexes[indexes+1] = i+2; 407 tri->indexes[indexes+2] = i+3; 408 tri->indexes[indexes+3] = i; 409 tri->indexes[indexes+4] = i+3; 410 tri->indexes[indexes+5] = i+1; 411 indexes += 6; 412 } 413 tri->numIndexes = indexes; 414 415 modelSurface_t surf; 416 surf.geometry = tri; 417 surf.shader = stage->material; 418 surf.id = 0; 419 420 renderEntity->hModel->AddSurface( surf ); 421 } 422 } 423 return true; 424 } 425 426 /* 427 ================ 428 idSmokeParticles::ModelCallback 429 ================ 430 */ 431 bool idSmokeParticles::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) { 432 // update the particles 433 if ( gameLocal.smokeParticles ) { 434 return gameLocal.smokeParticles->UpdateRenderEntity( renderEntity, renderView ); 435 } 436 437 return true; 438 }