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 "sys/platform.h"
30 #include "renderer/ModelManager.h"
31 #include "Game_local.h"
32
33 #include "SmokeParticles.h"
34
35 static const char *smokeParticle_SnapshotName = "_SmokeParticle_Snapshot_";
36
37 /*
38 ================
39 idSmokeParticles::idSmokeParticles
40 ================
41 */
idSmokeParticles(void)42 idSmokeParticles::idSmokeParticles( void ) {
43 initialized = false;
44 memset( &renderEntity, 0, sizeof( renderEntity ) );
45 renderEntityHandle = -1;
46 memset( smokes, 0, sizeof( smokes ) );
47 freeSmokes = NULL;
48 numActiveSmokes = 0;
49 currentParticleTime = -1;
50 }
51
52 /*
53 ================
54 idSmokeParticles::Init
55 ================
56 */
Init(void)57 void idSmokeParticles::Init( void ) {
58 if ( initialized ) {
59 Shutdown();
60 }
61
62 // set up the free list
63 for ( int i = 0; i < MAX_SMOKE_PARTICLES-1; i++ ) {
64 smokes[i].next = &smokes[i+1];
65 }
66 smokes[MAX_SMOKE_PARTICLES-1].next = NULL;
67 freeSmokes = &smokes[0];
68 numActiveSmokes = 0;
69
70 activeStages.Clear();
71
72 memset( &renderEntity, 0, sizeof( renderEntity ) );
73
74 renderEntity.bounds.Clear();
75 renderEntity.axis = mat3_identity;
76 renderEntity.shaderParms[ SHADERPARM_RED ] = 1;
77 renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1;
78 renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1;
79 renderEntity.shaderParms[3] = 1;
80
81 renderEntity.hModel = renderModelManager->AllocModel();
82 renderEntity.hModel->InitEmpty( smokeParticle_SnapshotName );
83
84 // we certainly don't want particle shadows
85 renderEntity.noShadow = 1;
86
87 // huge bounds, so it will be present in every world area
88 renderEntity.bounds.AddPoint( idVec3(-100000, -100000, -100000) );
89 renderEntity.bounds.AddPoint( idVec3( 100000, 100000, 100000) );
90
91 renderEntity.callback = idSmokeParticles::ModelCallback;
92 // add to renderer list
93 renderEntityHandle = gameRenderWorld->AddEntityDef( &renderEntity );
94
95 currentParticleTime = -1;
96
97 initialized = true;
98 }
99
100 /*
101 ================
102 idSmokeParticles::Shutdown
103 ================
104 */
Shutdown(void)105 void idSmokeParticles::Shutdown( void ) {
106 // make sure the render entity is freed before the model is freed
107 if ( renderEntityHandle != -1 ) {
108 gameRenderWorld->FreeEntityDef( renderEntityHandle );
109 renderEntityHandle = -1;
110 }
111 if ( renderEntity.hModel != NULL ) {
112 renderModelManager->FreeModel( renderEntity.hModel );
113 renderEntity.hModel = NULL;
114 }
115 initialized = false;
116 }
117
118 /*
119 ================
120 idSmokeParticles::FreeSmokes
121 ================
122 */
FreeSmokes(void)123 void idSmokeParticles::FreeSmokes( void ) {
124 for ( int activeStageNum = 0; activeStageNum < activeStages.Num(); activeStageNum++ ) {
125 singleSmoke_t *smoke, *next, *last;
126
127 activeSmokeStage_t *active = &activeStages[activeStageNum];
128 const idParticleStage *stage = active->stage;
129
130 for ( last = NULL, smoke = active->smokes; smoke; smoke = next ) {
131 next = smoke->next;
132
133 float frac = (float)( gameLocal.time - smoke->privateStartTime ) / ( stage->particleLife * 1000 );
134 if ( frac >= 1.0f ) {
135 // remove the particle from the stage list
136 if ( last != NULL ) {
137 last->next = smoke->next;
138 } else {
139 active->smokes = smoke->next;
140 }
141 // put the particle on the free list
142 smoke->next = freeSmokes;
143 freeSmokes = smoke;
144 numActiveSmokes--;
145 continue;
146 }
147
148 last = smoke;
149 }
150
151 if ( !active->smokes ) {
152 // remove this from the activeStages list
153 activeStages.RemoveIndex( activeStageNum );
154 activeStageNum--;
155 }
156 }
157 }
158
159 /*
160 ================
161 idSmokeParticles::EmitSmoke
162
163 Called by game code to drop another particle into the list
164 ================
165 */
EmitSmoke(const idDeclParticle * smoke,const int systemStartTime,const float diversity,const idVec3 & origin,const idMat3 & axis)166 bool idSmokeParticles::EmitSmoke( const idDeclParticle *smoke, const int systemStartTime, const float diversity, const idVec3 &origin, const idMat3 &axis ) {
167 bool continues = false;
168
169 if ( !smoke ) {
170 return false;
171 }
172
173 if ( !gameLocal.isNewFrame ) {
174 return false;
175 }
176
177 // dedicated doesn't smoke. No UpdateRenderEntity, so they would not be freed
178 if ( gameLocal.localClientNum < 0 ) {
179 return false;
180 }
181
182 assert( gameLocal.time == 0 || systemStartTime <= gameLocal.time );
183 if ( systemStartTime > gameLocal.time ) {
184 return false;
185 }
186
187 idRandom steppingRandom( 0xffff * diversity );
188
189 // for each stage in the smoke that is still emitting particles, emit a new singleSmoke_t
190 for ( int stageNum = 0; stageNum < smoke->stages.Num(); stageNum++ ) {
191 const idParticleStage *stage = smoke->stages[stageNum];
192
193 if ( !stage->cycleMsec ) {
194 continue;
195 }
196
197 if ( !stage->material ) {
198 continue;
199 }
200
201 if ( stage->particleLife <= 0 ) {
202 continue;
203 }
204
205 // see how many particles we should emit this tic
206 // FIXME: smoke.privateStartTime += stage->timeOffset;
207 int finalParticleTime = stage->cycleMsec * stage->spawnBunching;
208 int deltaMsec = gameLocal.time - systemStartTime;
209
210 int nowCount, prevCount;
211 if ( finalParticleTime == 0 ) {
212 // if spawnBunching is 0, they will all come out at once
213 if ( gameLocal.time == systemStartTime ) {
214 prevCount = -1;
215 nowCount = stage->totalParticles-1;
216 } else {
217 prevCount = stage->totalParticles;
218 }
219 } else {
220 nowCount = floor( ( (float)deltaMsec / finalParticleTime ) * stage->totalParticles );
221 if ( nowCount >= stage->totalParticles ) {
222 nowCount = stage->totalParticles-1;
223 }
224 prevCount = floor( ((float)( deltaMsec - USERCMD_MSEC ) / finalParticleTime) * stage->totalParticles );
225 if ( prevCount < -1 ) {
226 prevCount = -1;
227 }
228 }
229
230 if ( prevCount >= stage->totalParticles ) {
231 // no more particles from this stage
232 continue;
233 }
234
235 if ( nowCount < stage->totalParticles-1 ) {
236 // the system will need to emit particles next frame as well
237 continues = true;
238 }
239
240 // find an activeSmokeStage that matches this
241 activeSmokeStage_t *active = NULL;
242 int i;
243 for ( i = 0 ; i < activeStages.Num() ; i++ ) {
244 active = &activeStages[i];
245 if ( active->stage == stage ) {
246 break;
247 }
248 }
249 if ( i == activeStages.Num() ) {
250 // add a new one
251 activeSmokeStage_t newActive;
252
253 newActive.smokes = NULL;
254 newActive.stage = stage;
255 i = activeStages.Append( newActive );
256 active = &activeStages[i];
257 }
258
259 // add all the required particles
260 for ( prevCount++ ; prevCount <= nowCount ; prevCount++ ) {
261 if ( !freeSmokes ) {
262 gameLocal.Printf( "idSmokeParticles::EmitSmoke: no free smokes with %d active stages\n", activeStages.Num() );
263 return true;
264 }
265 singleSmoke_t *newSmoke = freeSmokes;
266 freeSmokes = freeSmokes->next;
267 numActiveSmokes++;
268
269 newSmoke->index = prevCount;
270 newSmoke->axis = axis;
271 newSmoke->origin = origin;
272 newSmoke->random = steppingRandom;
273 newSmoke->privateStartTime = systemStartTime + prevCount * finalParticleTime / stage->totalParticles;
274 newSmoke->next = active->smokes;
275 active->smokes = newSmoke;
276
277 steppingRandom.RandomInt(); // advance the random
278 }
279 }
280
281 return continues;
282 }
283
284 /*
285 ================
286 idSmokeParticles::UpdateRenderEntity
287 ================
288 */
UpdateRenderEntity(renderEntity_s * renderEntity,const renderView_t * renderView)289 bool idSmokeParticles::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) {
290
291 // FIXME: re-use model surfaces
292 renderEntity->hModel->InitEmpty( smokeParticle_SnapshotName );
293
294 // this may be triggered by a model trace or other non-view related source,
295 // to which we should look like an empty model
296 if ( !renderView ) {
297 return false;
298 }
299
300 // don't regenerate it if it is current
301 if ( renderView->time == currentParticleTime && !renderView->forceUpdate ) {
302 return false;
303 }
304 currentParticleTime = renderView->time;
305
306 particleGen_t g;
307
308 g.renderEnt = renderEntity;
309 g.renderView = renderView;
310
311 for ( int activeStageNum = 0; activeStageNum < activeStages.Num(); activeStageNum++ ) {
312 singleSmoke_t *smoke, *next, *last;
313
314 activeSmokeStage_t *active = &activeStages[activeStageNum];
315 const idParticleStage *stage = active->stage;
316
317 if ( !stage->material ) {
318 continue;
319 }
320
321 // allocate a srfTriangles that can hold all the particles
322 int count = 0;
323 for ( smoke = active->smokes; smoke; smoke = smoke->next ) {
324 count++;
325 }
326 int quads = count * stage->NumQuadsPerParticle();
327 srfTriangles_t *tri = renderEntity->hModel->AllocSurfaceTriangles( quads * 4, quads * 6 );
328 tri->numIndexes = quads * 6;
329 tri->numVerts = quads * 4;
330
331 // just always draw the particles
332 tri->bounds[0][0] =
333 tri->bounds[0][1] =
334 tri->bounds[0][2] = -99999;
335 tri->bounds[1][0] =
336 tri->bounds[1][1] =
337 tri->bounds[1][2] = 99999;
338
339 tri->numVerts = 0;
340 for ( last = NULL, smoke = active->smokes; smoke; smoke = next ) {
341 next = smoke->next;
342
343 g.frac = (float)( gameLocal.time - smoke->privateStartTime ) / ( stage->particleLife * 1000 );
344 if ( g.frac >= 1.0f ) {
345 // remove the particle from the stage list
346 if ( last != NULL ) {
347 last->next = smoke->next;
348 } else {
349 active->smokes = smoke->next;
350 }
351 // put the particle on the free list
352 smoke->next = freeSmokes;
353 freeSmokes = smoke;
354 numActiveSmokes--;
355 continue;
356 }
357
358 g.index = smoke->index;
359 g.random = smoke->random;
360
361 g.origin = smoke->origin;
362 g.axis = smoke->axis;
363
364 g.originalRandom = g.random;
365 g.age = g.frac * stage->particleLife;
366
367 tri->numVerts += stage->CreateParticle( &g, tri->verts + tri->numVerts );
368
369 last = smoke;
370 }
371 if ( tri->numVerts > quads * 4 ) {
372 gameLocal.Error( "idSmokeParticles::UpdateRenderEntity: miscounted verts" );
373 }
374
375 if ( tri->numVerts == 0 ) {
376
377 // they were all removed
378 renderEntity->hModel->FreeSurfaceTriangles( tri );
379
380 if ( !active->smokes ) {
381 // remove this from the activeStages list
382 activeStages.RemoveIndex( activeStageNum );
383 activeStageNum--;
384 }
385 } else {
386 // build the index list
387 int indexes = 0;
388 for ( int i = 0 ; i < tri->numVerts ; i += 4 ) {
389 tri->indexes[indexes+0] = i;
390 tri->indexes[indexes+1] = i+2;
391 tri->indexes[indexes+2] = i+3;
392 tri->indexes[indexes+3] = i;
393 tri->indexes[indexes+4] = i+3;
394 tri->indexes[indexes+5] = i+1;
395 indexes += 6;
396 }
397 tri->numIndexes = indexes;
398
399 modelSurface_t surf;
400 surf.geometry = tri;
401 surf.shader = stage->material;
402 surf.id = 0;
403
404 renderEntity->hModel->AddSurface( surf );
405 }
406 }
407 return true;
408 }
409
410 /*
411 ================
412 idSmokeParticles::ModelCallback
413 ================
414 */
ModelCallback(renderEntity_s * renderEntity,const renderView_t * renderView)415 bool idSmokeParticles::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) {
416 // update the particles
417 if ( gameLocal.smokeParticles ) {
418 return gameLocal.smokeParticles->UpdateRenderEntity( renderEntity, renderView );
419 }
420
421 return true;
422 }
423