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