]> icculus.org git repositories - icculus/iodoom3.git/blob - neo/d3xp/SmokeParticles.cpp
hello world
[icculus/iodoom3.git] / neo / d3xp / 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 #ifdef _D3XP
133                         float frac;
134
135                         if ( smoke->timeGroup ) {
136                                 frac = (float)( gameLocal.fast.time - smoke->privateStartTime ) / ( stage->particleLife * 1000 );
137                         }
138                         else {
139                                 frac = (float)( gameLocal.slow.time - smoke->privateStartTime ) / ( stage->particleLife * 1000 );
140                         }
141 #else
142                         float frac = (float)( gameLocal.time - smoke->privateStartTime ) / ( stage->particleLife * 1000 );
143 #endif
144                         if ( frac >= 1.0f ) {
145                                 // remove the particle from the stage list
146                                 if ( last != NULL ) {
147                                         last->next = smoke->next;
148                                 } else {
149                                         active->smokes = smoke->next;
150                                 }
151                                 // put the particle on the free list
152                                 smoke->next = freeSmokes;
153                                 freeSmokes = smoke;
154                                 numActiveSmokes--;
155                                 continue;
156                         }
157
158                         last = smoke;
159                 }
160
161                 if ( !active->smokes ) {
162                         // remove this from the activeStages list
163                         activeStages.RemoveIndex( activeStageNum );
164                         activeStageNum--;
165                 }
166         }
167 }
168
169 /*
170 ================
171 idSmokeParticles::EmitSmoke
172
173 Called by game code to drop another particle into the list
174 ================
175 */
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;
178 #ifdef _D3XP
179         SetTimeState ts( timeGroup );
180 #endif
181
182         if ( !smoke ) {
183                 return false;
184         }
185
186         if ( !gameLocal.isNewFrame ) {
187                 return false;
188         }
189
190         // dedicated doesn't smoke. No UpdateRenderEntity, so they would not be freed
191         if ( gameLocal.localClientNum < 0 ) {
192                 return false;
193         }
194
195         assert( gameLocal.time == 0 || systemStartTime <= gameLocal.time );
196         if ( systemStartTime > gameLocal.time ) {
197                 return false;
198         }
199
200         idRandom steppingRandom( 0xffff * diversity );
201
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];
205
206                 if ( !stage->cycleMsec ) {
207                         continue;
208                 }
209
210                 if ( !stage->material ) {
211                         continue;
212                 }
213
214                 if ( stage->particleLife <= 0 ) {
215                         continue;
216                 }
217
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;
222
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 ) {
227                                 prevCount = -1;
228                                 nowCount = stage->totalParticles-1;
229                         } else {
230                                 prevCount = stage->totalParticles;
231                         }
232                 } else {
233                         nowCount = floor( ( (float)deltaMsec / finalParticleTime ) * stage->totalParticles );
234                         if ( nowCount >= stage->totalParticles ) {
235                                 nowCount = stage->totalParticles-1;
236                         }
237                         prevCount = floor( ((float)( deltaMsec - gameLocal.msec /*_D3XP - FIX - was USERCMD_MSEC*/ ) / finalParticleTime) * stage->totalParticles );
238                         if ( prevCount < -1 ) {
239                                 prevCount = -1;
240                         }
241                 }
242
243                 if ( prevCount >= stage->totalParticles ) {
244                         // no more particles from this stage
245                         continue;
246                 }
247
248                 if ( nowCount < stage->totalParticles-1 ) {
249                         // the system will need to emit particles next frame as well
250                         continues = true;
251                 }
252
253                 // find an activeSmokeStage that matches this
254                 activeSmokeStage_t      *active;
255                 int i;
256                 for ( i = 0 ; i < activeStages.Num() ; i++ ) {
257                         active = &activeStages[i];
258                         if ( active->stage == stage ) {
259                                 break;
260                         }
261                 }
262                 if ( i == activeStages.Num() ) {
263                         // add a new one
264                         activeSmokeStage_t      newActive;
265
266                         newActive.smokes = NULL;
267                         newActive.stage = stage;
268                         i = activeStages.Append( newActive );
269                         active = &activeStages[i];
270                 }
271
272                 // add all the required particles
273                 for ( prevCount++ ; prevCount <= nowCount ; prevCount++ ) {
274                         if ( !freeSmokes ) {
275                                 gameLocal.Printf( "idSmokeParticles::EmitSmoke: no free smokes with %d active stages\n", activeStages.Num() );
276                                 return true;
277                         }
278                         singleSmoke_t   *newSmoke = freeSmokes;
279                         freeSmokes = freeSmokes->next;
280                         numActiveSmokes++;
281
282 #ifdef _D3XP
283                         newSmoke->timeGroup = timeGroup;
284 #endif
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;
292
293                         steppingRandom.RandomInt();     // advance the random
294                 }
295         }
296
297         return continues;
298 }
299
300 /*
301 ================
302 idSmokeParticles::UpdateRenderEntity
303 ================
304 */
305 bool idSmokeParticles::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) {
306
307         // FIXME: re-use model surfaces
308         renderEntity->hModel->InitEmpty( smokeParticle_SnapshotName );
309
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
312         if ( !renderView ) {
313                 return false;
314         }
315
316         // don't regenerate it if it is current
317         if ( renderView->time == currentParticleTime && !renderView->forceUpdate ) {
318                 return false;
319         }
320         currentParticleTime = renderView->time;
321
322         particleGen_t g;
323
324         g.renderEnt = renderEntity;
325         g.renderView = renderView;
326
327         for ( int activeStageNum = 0; activeStageNum < activeStages.Num(); activeStageNum++ ) {
328                 singleSmoke_t *smoke, *next, *last;
329
330                 activeSmokeStage_t *active = &activeStages[activeStageNum];
331                 const idParticleStage *stage = active->stage;
332
333                 if ( !stage->material ) {
334                         continue;
335                 }
336
337                 // allocate a srfTriangles that can hold all the particles
338                 int count = 0;
339                 for ( smoke = active->smokes; smoke; smoke = smoke->next ) {
340                         count++;
341                 }
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;
346
347                 // just always draw the particles
348                 tri->bounds[0][0] =
349                 tri->bounds[0][1] =
350                 tri->bounds[0][2] = -99999;
351                 tri->bounds[1][0] =
352                 tri->bounds[1][1] =
353                 tri->bounds[1][2] = 99999;
354
355                 tri->numVerts = 0;
356                 for ( last = NULL, smoke = active->smokes; smoke; smoke = next ) {
357                         next = smoke->next;
358
359 #ifdef _D3XP
360                         if ( smoke->timeGroup ) {
361                                 g.frac = (float)( gameLocal.fast.time - smoke->privateStartTime ) / (stage->particleLife * 1000);
362                         }
363                         else {
364                                 g.frac = (float)( gameLocal.time - smoke->privateStartTime ) / (stage->particleLife * 1000);
365                         }
366 #else
367                         g.frac = (float)( gameLocal.time - smoke->privateStartTime ) / (stage->particleLife * 1000);
368 #endif
369                         if ( g.frac >= 1.0f ) {
370                                 // remove the particle from the stage list
371                                 if ( last != NULL ) {
372                                         last->next = smoke->next;
373                                 } else {
374                                         active->smokes = smoke->next;
375                                 }
376                                 // put the particle on the free list
377                                 smoke->next = freeSmokes;
378                                 freeSmokes = smoke;
379                                 numActiveSmokes--;
380                                 continue;
381                         }
382
383                         g.index = smoke->index;
384                         g.random = smoke->random;
385
386                         g.origin = smoke->origin;
387                         g.axis = smoke->axis;
388
389                         g.originalRandom = g.random;
390                         g.age = g.frac * stage->particleLife;
391
392                         tri->numVerts += stage->CreateParticle( &g, tri->verts + tri->numVerts );
393
394                         last = smoke;
395                 }
396                 if ( tri->numVerts > quads * 4 ) {
397                         gameLocal.Error( "idSmokeParticles::UpdateRenderEntity: miscounted verts" );
398                 }
399
400                 if ( tri->numVerts == 0 ) {
401
402                         // they were all removed
403                         renderEntity->hModel->FreeSurfaceTriangles( tri );
404
405                         if ( !active->smokes ) {
406                                 // remove this from the activeStages list
407                                 activeStages.RemoveIndex( activeStageNum );
408                                 activeStageNum--;
409                         }
410                 } else {
411                         // build the index list
412                         int     indexes = 0;
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;
420                                 indexes += 6;
421                         }
422                         tri->numIndexes = indexes;
423
424                         modelSurface_t  surf;
425                         surf.geometry = tri;
426                         surf.shader = stage->material;
427                         surf.id = 0;
428
429                         renderEntity->hModel->AddSurface( surf );
430                 }
431         }
432         return true;
433 }
434
435 /*
436 ================
437 idSmokeParticles::ModelCallback
438 ================
439 */
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 );
444         }
445
446         return true;
447 }