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 ) {
132 float frac = (float)( gameLocal.time - smoke->privateStartTime ) / ( stage->particleLife * 1000 );
133 if ( frac >= 1.0f ) {
134 // remove the particle from the stage list
135 if ( last != NULL ) {
136 last->next = smoke->next;
138 active->smokes = smoke->next;
140 // put the particle on the free list
141 smoke->next = freeSmokes;
150 if ( !active->smokes ) {
151 // remove this from the activeStages list
152 activeStages.RemoveIndex( activeStageNum );
160 idSmokeParticles::EmitSmoke
162 Called by game code to drop another particle into the list
165 bool idSmokeParticles::EmitSmoke( const idDeclParticle *smoke, const int systemStartTime, const float diversity, const idVec3 &origin, const idMat3 &axis ) {
166 bool continues = false;
172 if ( !gameLocal.isNewFrame ) {
176 // dedicated doesn't smoke. No UpdateRenderEntity, so they would not be freed
177 if ( gameLocal.localClientNum < 0 ) {
181 assert( gameLocal.time == 0 || systemStartTime <= gameLocal.time );
182 if ( systemStartTime > gameLocal.time ) {
186 idRandom steppingRandom( 0xffff * diversity );
188 // for each stage in the smoke that is still emitting particles, emit a new singleSmoke_t
189 for ( int stageNum = 0; stageNum < smoke->stages.Num(); stageNum++ ) {
190 const idParticleStage *stage = smoke->stages[stageNum];
192 if ( !stage->cycleMsec ) {
196 if ( !stage->material ) {
200 if ( stage->particleLife <= 0 ) {
204 // see how many particles we should emit this tic
205 // FIXME: smoke.privateStartTime += stage->timeOffset;
206 int finalParticleTime = stage->cycleMsec * stage->spawnBunching;
207 int deltaMsec = gameLocal.time - systemStartTime;
209 int nowCount, prevCount;
210 if ( finalParticleTime == 0 ) {
211 // if spawnBunching is 0, they will all come out at once
212 if ( gameLocal.time == systemStartTime ) {
214 nowCount = stage->totalParticles-1;
216 prevCount = stage->totalParticles;
219 nowCount = floor( ( (float)deltaMsec / finalParticleTime ) * stage->totalParticles );
220 if ( nowCount >= stage->totalParticles ) {
221 nowCount = stage->totalParticles-1;
223 prevCount = floor( ((float)( deltaMsec - USERCMD_MSEC ) / finalParticleTime) * stage->totalParticles );
224 if ( prevCount < -1 ) {
229 if ( prevCount >= stage->totalParticles ) {
230 // no more particles from this stage
234 if ( nowCount < stage->totalParticles-1 ) {
235 // the system will need to emit particles next frame as well
239 // find an activeSmokeStage that matches this
240 activeSmokeStage_t *active;
242 for ( i = 0 ; i < activeStages.Num() ; i++ ) {
243 active = &activeStages[i];
244 if ( active->stage == stage ) {
248 if ( i == activeStages.Num() ) {
250 activeSmokeStage_t newActive;
252 newActive.smokes = NULL;
253 newActive.stage = stage;
254 i = activeStages.Append( newActive );
255 active = &activeStages[i];
258 // add all the required particles
259 for ( prevCount++ ; prevCount <= nowCount ; prevCount++ ) {
261 gameLocal.Printf( "idSmokeParticles::EmitSmoke: no free smokes with %d active stages\n", activeStages.Num() );
264 singleSmoke_t *newSmoke = freeSmokes;
265 freeSmokes = freeSmokes->next;
268 newSmoke->index = prevCount;
269 newSmoke->axis = axis;
270 newSmoke->origin = origin;
271 newSmoke->random = steppingRandom;
272 newSmoke->privateStartTime = systemStartTime + prevCount * finalParticleTime / stage->totalParticles;
273 newSmoke->next = active->smokes;
274 active->smokes = newSmoke;
276 steppingRandom.RandomInt(); // advance the random
285 idSmokeParticles::UpdateRenderEntity
288 bool idSmokeParticles::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) {
290 // FIXME: re-use model surfaces
291 renderEntity->hModel->InitEmpty( smokeParticle_SnapshotName );
293 // this may be triggered by a model trace or other non-view related source,
294 // to which we should look like an empty model
299 // don't regenerate it if it is current
300 if ( renderView->time == currentParticleTime && !renderView->forceUpdate ) {
303 currentParticleTime = renderView->time;
307 g.renderEnt = renderEntity;
308 g.renderView = renderView;
310 for ( int activeStageNum = 0; activeStageNum < activeStages.Num(); activeStageNum++ ) {
311 singleSmoke_t *smoke, *next, *last;
313 activeSmokeStage_t *active = &activeStages[activeStageNum];
314 const idParticleStage *stage = active->stage;
316 if ( !stage->material ) {
320 // allocate a srfTriangles that can hold all the particles
322 for ( smoke = active->smokes; smoke; smoke = smoke->next ) {
325 int quads = count * stage->NumQuadsPerParticle();
326 srfTriangles_t *tri = renderEntity->hModel->AllocSurfaceTriangles( quads * 4, quads * 6 );
327 tri->numIndexes = quads * 6;
328 tri->numVerts = quads * 4;
330 // just always draw the particles
333 tri->bounds[0][2] = -99999;
336 tri->bounds[1][2] = 99999;
339 for ( last = NULL, smoke = active->smokes; smoke; smoke = next ) {
342 g.frac = (float)( gameLocal.time - smoke->privateStartTime ) / ( stage->particleLife * 1000 );
343 if ( g.frac >= 1.0f ) {
344 // remove the particle from the stage list
345 if ( last != NULL ) {
346 last->next = smoke->next;
348 active->smokes = smoke->next;
350 // put the particle on the free list
351 smoke->next = freeSmokes;
357 g.index = smoke->index;
358 g.random = smoke->random;
360 g.origin = smoke->origin;
361 g.axis = smoke->axis;
363 g.originalRandom = g.random;
364 g.age = g.frac * stage->particleLife;
366 tri->numVerts += stage->CreateParticle( &g, tri->verts + tri->numVerts );
370 if ( tri->numVerts > quads * 4 ) {
371 gameLocal.Error( "idSmokeParticles::UpdateRenderEntity: miscounted verts" );
374 if ( tri->numVerts == 0 ) {
376 // they were all removed
377 renderEntity->hModel->FreeSurfaceTriangles( tri );
379 if ( !active->smokes ) {
380 // remove this from the activeStages list
381 activeStages.RemoveIndex( activeStageNum );
385 // build the index list
387 for ( int i = 0 ; i < tri->numVerts ; i += 4 ) {
388 tri->indexes[indexes+0] = i;
389 tri->indexes[indexes+1] = i+2;
390 tri->indexes[indexes+2] = i+3;
391 tri->indexes[indexes+3] = i;
392 tri->indexes[indexes+4] = i+3;
393 tri->indexes[indexes+5] = i+1;
396 tri->numIndexes = indexes;
400 surf.shader = stage->material;
403 renderEntity->hModel->AddSurface( surf );
411 idSmokeParticles::ModelCallback
414 bool idSmokeParticles::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) {
415 // update the particles
416 if ( gameLocal.smokeParticles ) {
417 return gameLocal.smokeParticles->UpdateRenderEntity( renderEntity, renderView );