DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

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 }