]> icculus.org git repositories - icculus/iodoom3.git/blob - neo/game/SmokeParticles.cpp
hello world
[icculus/iodoom3.git] / neo / game / SmokeParticles.cpp
1 /*
2 ===========================================================================
3
4 Doom 3 GPL Source Code
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. 
6
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).  
8
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.
13
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.
18
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/>.
21
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.
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( void ) {
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( void ) {
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( 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;
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( void ) {
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 = (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;
137                                 } else {
138                                         active->smokes = smoke->next;
139                                 }
140                                 // put the particle on the free list
141                                 smoke->next = freeSmokes;
142                                 freeSmokes = smoke;
143                                 numActiveSmokes--;
144                                 continue;
145                         }
146
147                         last = smoke;
148                 }
149
150                 if ( !active->smokes ) {
151                         // remove this from the activeStages list
152                         activeStages.RemoveIndex( activeStageNum );
153                         activeStageNum--;
154                 }
155         }
156 }
157
158 /*
159 ================
160 idSmokeParticles::EmitSmoke
161
162 Called by game code to drop another particle into the list
163 ================
164 */
165 bool idSmokeParticles::EmitSmoke( const idDeclParticle *smoke, const int systemStartTime, const float diversity, const idVec3 &origin, const idMat3 &axis ) {
166         bool    continues = false;
167
168         if ( !smoke ) {
169                 return false;
170         }
171
172         if ( !gameLocal.isNewFrame ) {
173                 return false;
174         }
175
176         // dedicated doesn't smoke. No UpdateRenderEntity, so they would not be freed
177         if ( gameLocal.localClientNum < 0 ) {
178                 return false;
179         }
180
181         assert( gameLocal.time == 0 || systemStartTime <= gameLocal.time );
182         if ( systemStartTime > gameLocal.time ) {
183                 return false;
184         }
185
186         idRandom steppingRandom( 0xffff * diversity );
187
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];
191
192                 if ( !stage->cycleMsec ) {
193                         continue;
194                 }
195
196                 if ( !stage->material ) {
197                         continue;
198                 }
199
200                 if ( stage->particleLife <= 0 ) {
201                         continue;
202                 }
203
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;
208
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 ) {
213                                 prevCount = -1;
214                                 nowCount = stage->totalParticles-1;
215                         } else {
216                                 prevCount = stage->totalParticles;
217                         }
218                 } else {
219                         nowCount = floor( ( (float)deltaMsec / finalParticleTime ) * stage->totalParticles );
220                         if ( nowCount >= stage->totalParticles ) {
221                                 nowCount = stage->totalParticles-1;
222                         }
223                         prevCount = floor( ((float)( deltaMsec - USERCMD_MSEC ) / finalParticleTime) * stage->totalParticles );
224                         if ( prevCount < -1 ) {
225                                 prevCount = -1;
226                         }
227                 }
228
229                 if ( prevCount >= stage->totalParticles ) {
230                         // no more particles from this stage
231                         continue;
232                 }
233
234                 if ( nowCount < stage->totalParticles-1 ) {
235                         // the system will need to emit particles next frame as well
236                         continues = true;
237                 }
238
239                 // find an activeSmokeStage that matches this
240                 activeSmokeStage_t      *active;
241                 int i;
242                 for ( i = 0 ; i < activeStages.Num() ; i++ ) {
243                         active = &activeStages[i];
244                         if ( active->stage == stage ) {
245                                 break;
246                         }
247                 }
248                 if ( i == activeStages.Num() ) {
249                         // add a new one
250                         activeSmokeStage_t      newActive;
251
252                         newActive.smokes = NULL;
253                         newActive.stage = stage;
254                         i = activeStages.Append( newActive );
255                         active = &activeStages[i];
256                 }
257
258                 // add all the required particles
259                 for ( prevCount++ ; prevCount <= nowCount ; prevCount++ ) {
260                         if ( !freeSmokes ) {
261                                 gameLocal.Printf( "idSmokeParticles::EmitSmoke: no free smokes with %d active stages\n", activeStages.Num() );
262                                 return true;
263                         }
264                         singleSmoke_t   *newSmoke = freeSmokes;
265                         freeSmokes = freeSmokes->next;
266                         numActiveSmokes++;
267
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;
275
276                         steppingRandom.RandomInt();     // advance the random
277                 }
278         }
279
280         return continues;
281 }
282
283 /*
284 ================
285 idSmokeParticles::UpdateRenderEntity
286 ================
287 */
288 bool idSmokeParticles::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) {
289
290         // FIXME: re-use model surfaces
291         renderEntity->hModel->InitEmpty( smokeParticle_SnapshotName );
292
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
295         if ( !renderView ) {
296                 return false;
297         }
298
299         // don't regenerate it if it is current
300         if ( renderView->time == currentParticleTime && !renderView->forceUpdate ) {
301                 return false;
302         }
303         currentParticleTime = renderView->time;
304
305         particleGen_t g;
306
307         g.renderEnt = renderEntity;
308         g.renderView = renderView;
309
310         for ( int activeStageNum = 0; activeStageNum < activeStages.Num(); activeStageNum++ ) {
311                 singleSmoke_t *smoke, *next, *last;
312
313                 activeSmokeStage_t *active = &activeStages[activeStageNum];
314                 const idParticleStage *stage = active->stage;
315
316                 if ( !stage->material ) {
317                         continue;
318                 }
319
320                 // allocate a srfTriangles that can hold all the particles
321                 int count = 0;
322                 for ( smoke = active->smokes; smoke; smoke = smoke->next ) {
323                         count++;
324                 }
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;
329
330                 // just always draw the particles
331                 tri->bounds[0][0] =
332                 tri->bounds[0][1] =
333                 tri->bounds[0][2] = -99999;
334                 tri->bounds[1][0] =
335                 tri->bounds[1][1] =
336                 tri->bounds[1][2] = 99999;
337
338                 tri->numVerts = 0;
339                 for ( last = NULL, smoke = active->smokes; smoke; smoke = next ) {
340                         next = smoke->next;
341
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;
347                                 } else {
348                                         active->smokes = smoke->next;
349                                 }
350                                 // put the particle on the free list
351                                 smoke->next = freeSmokes;
352                                 freeSmokes = smoke;
353                                 numActiveSmokes--;
354                                 continue;
355                         }
356
357                         g.index = smoke->index;
358                         g.random = smoke->random;
359
360                         g.origin = smoke->origin;
361                         g.axis = smoke->axis;
362
363                         g.originalRandom = g.random;
364                         g.age = g.frac * stage->particleLife;
365
366                         tri->numVerts += stage->CreateParticle( &g, tri->verts + tri->numVerts );
367
368                         last = smoke;
369                 }
370                 if ( tri->numVerts > quads * 4 ) {
371                         gameLocal.Error( "idSmokeParticles::UpdateRenderEntity: miscounted verts" );
372                 }
373
374                 if ( tri->numVerts == 0 ) {
375
376                         // they were all removed
377                         renderEntity->hModel->FreeSurfaceTriangles( tri );
378
379                         if ( !active->smokes ) {
380                                 // remove this from the activeStages list
381                                 activeStages.RemoveIndex( activeStageNum );
382                                 activeStageNum--;
383                         }
384                 } else {
385                         // build the index list
386                         int     indexes = 0;
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;
394                                 indexes += 6;
395                         }
396                         tri->numIndexes = indexes;
397
398                         modelSurface_t  surf;
399                         surf.geometry = tri;
400                         surf.shader = stage->material;
401                         surf.id = 0;
402
403                         renderEntity->hModel->AddSurface( surf );
404                 }
405         }
406         return true;
407 }
408
409 /*
410 ================
411 idSmokeParticles::ModelCallback
412 ================
413 */
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 );
418         }
419
420         return true;
421 }