2 ===========================================================================
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
9 Doom 3 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.
14 Doom 3 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.
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
22 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.
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.
26 ===========================================================================
29 #include "../idlib/precompiled.h"
32 #include "Game_local.h"
34 static const char *smokeParticle_SnapshotName = "_SmokeParticle_Snapshot_";
38 idSmokeParticles::idSmokeParticles
41 idSmokeParticles::idSmokeParticles( void ) {
43 memset( &renderEntity, 0, sizeof( renderEntity ) );
44 renderEntityHandle = -1;
45 memset( smokes, 0, sizeof( smokes ) );
48 currentParticleTime = -1;
53 idSmokeParticles::Init
56 void idSmokeParticles::Init( void ) {
61 // set up the free list
62 for ( int i = 0; i < MAX_SMOKE_PARTICLES-1; i++ ) {
63 smokes[i].next = &smokes[i+1];
65 smokes[MAX_SMOKE_PARTICLES-1].next = NULL;
66 freeSmokes = &smokes[0];
71 memset( &renderEntity, 0, sizeof( renderEntity ) );
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;
80 renderEntity.hModel = renderModelManager->AllocModel();
81 renderEntity.hModel->InitEmpty( smokeParticle_SnapshotName );
83 // we certainly don't want particle shadows
84 renderEntity.noShadow = 1;
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) );
90 renderEntity.callback = idSmokeParticles::ModelCallback;
91 // add to renderer list
92 renderEntityHandle = gameRenderWorld->AddEntityDef( &renderEntity );
94 currentParticleTime = -1;
101 idSmokeParticles::Shutdown
104 void idSmokeParticles::Shutdown( void ) {
105 // make sure the render entity is freed before the model is freed
106 if ( renderEntityHandle != -1 ) {
107 gameRenderWorld->FreeEntityDef( renderEntityHandle );
108 renderEntityHandle = -1;
110 if ( renderEntity.hModel != NULL ) {
111 renderModelManager->FreeModel( renderEntity.hModel );
112 renderEntity.hModel = NULL;
119 idSmokeParticles::FreeSmokes
122 void idSmokeParticles::FreeSmokes( void ) {
123 for ( int activeStageNum = 0; activeStageNum < activeStages.Num(); activeStageNum++ ) {
124 singleSmoke_t *smoke, *next, *last;
126 activeSmokeStage_t *active = &activeStages[activeStageNum];
127 const idParticleStage *stage = active->stage;
129 for ( last = NULL, smoke = active->smokes; smoke; smoke = next ) {
135 if ( smoke->timeGroup ) {
136 frac = (float)( gameLocal.fast.time - smoke->privateStartTime ) / ( stage->particleLife * 1000 );
139 frac = (float)( gameLocal.slow.time - smoke->privateStartTime ) / ( stage->particleLife * 1000 );
142 float frac = (float)( gameLocal.time - smoke->privateStartTime ) / ( stage->particleLife * 1000 );
144 if ( frac >= 1.0f ) {
145 // remove the particle from the stage list
146 if ( last != NULL ) {
147 last->next = smoke->next;
149 active->smokes = smoke->next;
151 // put the particle on the free list
152 smoke->next = freeSmokes;
161 if ( !active->smokes ) {
162 // remove this from the activeStages list
163 activeStages.RemoveIndex( activeStageNum );
171 idSmokeParticles::EmitSmoke
173 Called by game code to drop another particle into the list
176 bool idSmokeParticles::EmitSmoke( const idDeclParticle *smoke, const int systemStartTime, const float diversity, const idVec3 &origin, const idMat3 &axis, int timeGroup /*_D3XP*/ ) {
177 bool continues = false;
179 SetTimeState ts( timeGroup );
186 if ( !gameLocal.isNewFrame ) {
190 // dedicated doesn't smoke. No UpdateRenderEntity, so they would not be freed
191 if ( gameLocal.localClientNum < 0 ) {
195 assert( gameLocal.time == 0 || systemStartTime <= gameLocal.time );
196 if ( systemStartTime > gameLocal.time ) {
200 idRandom steppingRandom( 0xffff * diversity );
202 // for each stage in the smoke that is still emitting particles, emit a new singleSmoke_t
203 for ( int stageNum = 0; stageNum < smoke->stages.Num(); stageNum++ ) {
204 const idParticleStage *stage = smoke->stages[stageNum];
206 if ( !stage->cycleMsec ) {
210 if ( !stage->material ) {
214 if ( stage->particleLife <= 0 ) {
218 // see how many particles we should emit this tic
219 // FIXME: smoke.privateStartTime += stage->timeOffset;
220 int finalParticleTime = stage->cycleMsec * stage->spawnBunching;
221 int deltaMsec = gameLocal.time - systemStartTime;
223 int nowCount, prevCount;
224 if ( finalParticleTime == 0 ) {
225 // if spawnBunching is 0, they will all come out at once
226 if ( gameLocal.time == systemStartTime ) {
228 nowCount = stage->totalParticles-1;
230 prevCount = stage->totalParticles;
233 nowCount = floor( ( (float)deltaMsec / finalParticleTime ) * stage->totalParticles );
234 if ( nowCount >= stage->totalParticles ) {
235 nowCount = stage->totalParticles-1;
237 prevCount = floor( ((float)( deltaMsec - gameLocal.msec /*_D3XP - FIX - was USERCMD_MSEC*/ ) / finalParticleTime) * stage->totalParticles );
238 if ( prevCount < -1 ) {
243 if ( prevCount >= stage->totalParticles ) {
244 // no more particles from this stage
248 if ( nowCount < stage->totalParticles-1 ) {
249 // the system will need to emit particles next frame as well
253 // find an activeSmokeStage that matches this
254 activeSmokeStage_t *active;
256 for ( i = 0 ; i < activeStages.Num() ; i++ ) {
257 active = &activeStages[i];
258 if ( active->stage == stage ) {
262 if ( i == activeStages.Num() ) {
264 activeSmokeStage_t newActive;
266 newActive.smokes = NULL;
267 newActive.stage = stage;
268 i = activeStages.Append( newActive );
269 active = &activeStages[i];
272 // add all the required particles
273 for ( prevCount++ ; prevCount <= nowCount ; prevCount++ ) {
275 gameLocal.Printf( "idSmokeParticles::EmitSmoke: no free smokes with %d active stages\n", activeStages.Num() );
278 singleSmoke_t *newSmoke = freeSmokes;
279 freeSmokes = freeSmokes->next;
283 newSmoke->timeGroup = timeGroup;
285 newSmoke->index = prevCount;
286 newSmoke->axis = axis;
287 newSmoke->origin = origin;
288 newSmoke->random = steppingRandom;
289 newSmoke->privateStartTime = systemStartTime + prevCount * finalParticleTime / stage->totalParticles;
290 newSmoke->next = active->smokes;
291 active->smokes = newSmoke;
293 steppingRandom.RandomInt(); // advance the random
302 idSmokeParticles::UpdateRenderEntity
305 bool idSmokeParticles::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) {
307 // FIXME: re-use model surfaces
308 renderEntity->hModel->InitEmpty( smokeParticle_SnapshotName );
310 // this may be triggered by a model trace or other non-view related source,
311 // to which we should look like an empty model
316 // don't regenerate it if it is current
317 if ( renderView->time == currentParticleTime && !renderView->forceUpdate ) {
320 currentParticleTime = renderView->time;
324 g.renderEnt = renderEntity;
325 g.renderView = renderView;
327 for ( int activeStageNum = 0; activeStageNum < activeStages.Num(); activeStageNum++ ) {
328 singleSmoke_t *smoke, *next, *last;
330 activeSmokeStage_t *active = &activeStages[activeStageNum];
331 const idParticleStage *stage = active->stage;
333 if ( !stage->material ) {
337 // allocate a srfTriangles that can hold all the particles
339 for ( smoke = active->smokes; smoke; smoke = smoke->next ) {
342 int quads = count * stage->NumQuadsPerParticle();
343 srfTriangles_t *tri = renderEntity->hModel->AllocSurfaceTriangles( quads * 4, quads * 6 );
344 tri->numIndexes = quads * 6;
345 tri->numVerts = quads * 4;
347 // just always draw the particles
350 tri->bounds[0][2] = -99999;
353 tri->bounds[1][2] = 99999;
356 for ( last = NULL, smoke = active->smokes; smoke; smoke = next ) {
360 if ( smoke->timeGroup ) {
361 g.frac = (float)( gameLocal.fast.time - smoke->privateStartTime ) / (stage->particleLife * 1000);
364 g.frac = (float)( gameLocal.time - smoke->privateStartTime ) / (stage->particleLife * 1000);
367 g.frac = (float)( gameLocal.time - smoke->privateStartTime ) / (stage->particleLife * 1000);
369 if ( g.frac >= 1.0f ) {
370 // remove the particle from the stage list
371 if ( last != NULL ) {
372 last->next = smoke->next;
374 active->smokes = smoke->next;
376 // put the particle on the free list
377 smoke->next = freeSmokes;
383 g.index = smoke->index;
384 g.random = smoke->random;
386 g.origin = smoke->origin;
387 g.axis = smoke->axis;
389 g.originalRandom = g.random;
390 g.age = g.frac * stage->particleLife;
392 tri->numVerts += stage->CreateParticle( &g, tri->verts + tri->numVerts );
396 if ( tri->numVerts > quads * 4 ) {
397 gameLocal.Error( "idSmokeParticles::UpdateRenderEntity: miscounted verts" );
400 if ( tri->numVerts == 0 ) {
402 // they were all removed
403 renderEntity->hModel->FreeSurfaceTriangles( tri );
405 if ( !active->smokes ) {
406 // remove this from the activeStages list
407 activeStages.RemoveIndex( activeStageNum );
411 // build the index list
413 for ( int i = 0 ; i < tri->numVerts ; i += 4 ) {
414 tri->indexes[indexes+0] = i;
415 tri->indexes[indexes+1] = i+2;
416 tri->indexes[indexes+2] = i+3;
417 tri->indexes[indexes+3] = i;
418 tri->indexes[indexes+4] = i+3;
419 tri->indexes[indexes+5] = i+1;
422 tri->numIndexes = indexes;
426 surf.shader = stage->material;
429 renderEntity->hModel->AddSurface( surf );
437 idSmokeParticles::ModelCallback
440 bool idSmokeParticles::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) {
441 // update the particles
442 if ( gameLocal.smokeParticles ) {
443 return gameLocal.smokeParticles->UpdateRenderEntity( renderEntity, renderView );