SmokeParticles.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. /*
  2. ===========================================================================
  3. Doom 3 GPL Source Code
  4. Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
  5. This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
  6. Doom 3 Source Code is free software: you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation, either version 3 of the License, or
  9. (at your option) any later version.
  10. Doom 3 Source Code is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
  16. 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.
  17. 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.
  18. ===========================================================================
  19. */
  20. #include "../idlib/precompiled.h"
  21. #pragma hdrstop
  22. #include "Game_local.h"
  23. static const char *smokeParticle_SnapshotName = "_SmokeParticle_Snapshot_";
  24. /*
  25. ================
  26. idSmokeParticles::idSmokeParticles
  27. ================
  28. */
  29. idSmokeParticles::idSmokeParticles( void ) {
  30. initialized = false;
  31. memset( &renderEntity, 0, sizeof( renderEntity ) );
  32. renderEntityHandle = -1;
  33. memset( smokes, 0, sizeof( smokes ) );
  34. freeSmokes = NULL;
  35. numActiveSmokes = 0;
  36. currentParticleTime = -1;
  37. }
  38. /*
  39. ================
  40. idSmokeParticles::Init
  41. ================
  42. */
  43. void idSmokeParticles::Init( void ) {
  44. if ( initialized ) {
  45. Shutdown();
  46. }
  47. // set up the free list
  48. for ( int i = 0; i < MAX_SMOKE_PARTICLES-1; i++ ) {
  49. smokes[i].next = &smokes[i+1];
  50. }
  51. smokes[MAX_SMOKE_PARTICLES-1].next = NULL;
  52. freeSmokes = &smokes[0];
  53. numActiveSmokes = 0;
  54. activeStages.Clear();
  55. memset( &renderEntity, 0, sizeof( renderEntity ) );
  56. renderEntity.bounds.Clear();
  57. renderEntity.axis = mat3_identity;
  58. renderEntity.shaderParms[ SHADERPARM_RED ] = 1;
  59. renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1;
  60. renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1;
  61. renderEntity.shaderParms[3] = 1;
  62. renderEntity.hModel = renderModelManager->AllocModel();
  63. renderEntity.hModel->InitEmpty( smokeParticle_SnapshotName );
  64. // we certainly don't want particle shadows
  65. renderEntity.noShadow = 1;
  66. // huge bounds, so it will be present in every world area
  67. renderEntity.bounds.AddPoint( idVec3(-100000, -100000, -100000) );
  68. renderEntity.bounds.AddPoint( idVec3( 100000, 100000, 100000) );
  69. renderEntity.callback = idSmokeParticles::ModelCallback;
  70. // add to renderer list
  71. renderEntityHandle = gameRenderWorld->AddEntityDef( &renderEntity );
  72. currentParticleTime = -1;
  73. initialized = true;
  74. }
  75. /*
  76. ================
  77. idSmokeParticles::Shutdown
  78. ================
  79. */
  80. void idSmokeParticles::Shutdown( void ) {
  81. // make sure the render entity is freed before the model is freed
  82. if ( renderEntityHandle != -1 ) {
  83. gameRenderWorld->FreeEntityDef( renderEntityHandle );
  84. renderEntityHandle = -1;
  85. }
  86. if ( renderEntity.hModel != NULL ) {
  87. renderModelManager->FreeModel( renderEntity.hModel );
  88. renderEntity.hModel = NULL;
  89. }
  90. initialized = false;
  91. }
  92. /*
  93. ================
  94. idSmokeParticles::FreeSmokes
  95. ================
  96. */
  97. void idSmokeParticles::FreeSmokes( void ) {
  98. for ( int activeStageNum = 0; activeStageNum < activeStages.Num(); activeStageNum++ ) {
  99. singleSmoke_t *smoke, *next, *last;
  100. activeSmokeStage_t *active = &activeStages[activeStageNum];
  101. const idParticleStage *stage = active->stage;
  102. for ( last = NULL, smoke = active->smokes; smoke; smoke = next ) {
  103. next = smoke->next;
  104. #ifdef _D3XP
  105. float frac;
  106. if ( smoke->timeGroup ) {
  107. frac = (float)( gameLocal.fast.time - smoke->privateStartTime ) / ( stage->particleLife * 1000 );
  108. }
  109. else {
  110. frac = (float)( gameLocal.slow.time - smoke->privateStartTime ) / ( stage->particleLife * 1000 );
  111. }
  112. #else
  113. float frac = (float)( gameLocal.time - smoke->privateStartTime ) / ( stage->particleLife * 1000 );
  114. #endif
  115. if ( frac >= 1.0f ) {
  116. // remove the particle from the stage list
  117. if ( last != NULL ) {
  118. last->next = smoke->next;
  119. } else {
  120. active->smokes = smoke->next;
  121. }
  122. // put the particle on the free list
  123. smoke->next = freeSmokes;
  124. freeSmokes = smoke;
  125. numActiveSmokes--;
  126. continue;
  127. }
  128. last = smoke;
  129. }
  130. if ( !active->smokes ) {
  131. // remove this from the activeStages list
  132. activeStages.RemoveIndex( activeStageNum );
  133. activeStageNum--;
  134. }
  135. }
  136. }
  137. /*
  138. ================
  139. idSmokeParticles::EmitSmoke
  140. Called by game code to drop another particle into the list
  141. ================
  142. */
  143. bool idSmokeParticles::EmitSmoke( const idDeclParticle *smoke, const int systemStartTime, const float diversity, const idVec3 &origin, const idMat3 &axis, int timeGroup /*_D3XP*/ ) {
  144. bool continues = false;
  145. #ifdef _D3XP
  146. SetTimeState ts( timeGroup );
  147. #endif
  148. if ( !smoke ) {
  149. return false;
  150. }
  151. if ( !gameLocal.isNewFrame ) {
  152. return false;
  153. }
  154. // dedicated doesn't smoke. No UpdateRenderEntity, so they would not be freed
  155. if ( gameLocal.localClientNum < 0 ) {
  156. return false;
  157. }
  158. assert( gameLocal.time == 0 || systemStartTime <= gameLocal.time );
  159. if ( systemStartTime > gameLocal.time ) {
  160. return false;
  161. }
  162. idRandom steppingRandom( 0xffff * diversity );
  163. // for each stage in the smoke that is still emitting particles, emit a new singleSmoke_t
  164. for ( int stageNum = 0; stageNum < smoke->stages.Num(); stageNum++ ) {
  165. const idParticleStage *stage = smoke->stages[stageNum];
  166. if ( !stage->cycleMsec ) {
  167. continue;
  168. }
  169. if ( !stage->material ) {
  170. continue;
  171. }
  172. if ( stage->particleLife <= 0 ) {
  173. continue;
  174. }
  175. // see how many particles we should emit this tic
  176. // FIXME: smoke.privateStartTime += stage->timeOffset;
  177. int finalParticleTime = stage->cycleMsec * stage->spawnBunching;
  178. int deltaMsec = gameLocal.time - systemStartTime;
  179. int nowCount, prevCount;
  180. if ( finalParticleTime == 0 ) {
  181. // if spawnBunching is 0, they will all come out at once
  182. if ( gameLocal.time == systemStartTime ) {
  183. prevCount = -1;
  184. nowCount = stage->totalParticles-1;
  185. } else {
  186. prevCount = stage->totalParticles;
  187. }
  188. } else {
  189. nowCount = floor( ( (float)deltaMsec / finalParticleTime ) * stage->totalParticles );
  190. if ( nowCount >= stage->totalParticles ) {
  191. nowCount = stage->totalParticles-1;
  192. }
  193. prevCount = floor( ((float)( deltaMsec - gameLocal.msec /*_D3XP - FIX - was USERCMD_MSEC*/ ) / finalParticleTime) * stage->totalParticles );
  194. if ( prevCount < -1 ) {
  195. prevCount = -1;
  196. }
  197. }
  198. if ( prevCount >= stage->totalParticles ) {
  199. // no more particles from this stage
  200. continue;
  201. }
  202. if ( nowCount < stage->totalParticles-1 ) {
  203. // the system will need to emit particles next frame as well
  204. continues = true;
  205. }
  206. // find an activeSmokeStage that matches this
  207. activeSmokeStage_t *active;
  208. int i;
  209. for ( i = 0 ; i < activeStages.Num() ; i++ ) {
  210. active = &activeStages[i];
  211. if ( active->stage == stage ) {
  212. break;
  213. }
  214. }
  215. if ( i == activeStages.Num() ) {
  216. // add a new one
  217. activeSmokeStage_t newActive;
  218. newActive.smokes = NULL;
  219. newActive.stage = stage;
  220. i = activeStages.Append( newActive );
  221. active = &activeStages[i];
  222. }
  223. // add all the required particles
  224. for ( prevCount++ ; prevCount <= nowCount ; prevCount++ ) {
  225. if ( !freeSmokes ) {
  226. gameLocal.Printf( "idSmokeParticles::EmitSmoke: no free smokes with %d active stages\n", activeStages.Num() );
  227. return true;
  228. }
  229. singleSmoke_t *newSmoke = freeSmokes;
  230. freeSmokes = freeSmokes->next;
  231. numActiveSmokes++;
  232. #ifdef _D3XP
  233. newSmoke->timeGroup = timeGroup;
  234. #endif
  235. newSmoke->index = prevCount;
  236. newSmoke->axis = axis;
  237. newSmoke->origin = origin;
  238. newSmoke->random = steppingRandom;
  239. newSmoke->privateStartTime = systemStartTime + prevCount * finalParticleTime / stage->totalParticles;
  240. newSmoke->next = active->smokes;
  241. active->smokes = newSmoke;
  242. steppingRandom.RandomInt(); // advance the random
  243. }
  244. }
  245. return continues;
  246. }
  247. /*
  248. ================
  249. idSmokeParticles::UpdateRenderEntity
  250. ================
  251. */
  252. bool idSmokeParticles::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) {
  253. // FIXME: re-use model surfaces
  254. renderEntity->hModel->InitEmpty( smokeParticle_SnapshotName );
  255. // this may be triggered by a model trace or other non-view related source,
  256. // to which we should look like an empty model
  257. if ( !renderView ) {
  258. return false;
  259. }
  260. // don't regenerate it if it is current
  261. if ( renderView->time == currentParticleTime && !renderView->forceUpdate ) {
  262. return false;
  263. }
  264. currentParticleTime = renderView->time;
  265. particleGen_t g;
  266. g.renderEnt = renderEntity;
  267. g.renderView = renderView;
  268. for ( int activeStageNum = 0; activeStageNum < activeStages.Num(); activeStageNum++ ) {
  269. singleSmoke_t *smoke, *next, *last;
  270. activeSmokeStage_t *active = &activeStages[activeStageNum];
  271. const idParticleStage *stage = active->stage;
  272. if ( !stage->material ) {
  273. continue;
  274. }
  275. // allocate a srfTriangles that can hold all the particles
  276. int count = 0;
  277. for ( smoke = active->smokes; smoke; smoke = smoke->next ) {
  278. count++;
  279. }
  280. int quads = count * stage->NumQuadsPerParticle();
  281. srfTriangles_t *tri = renderEntity->hModel->AllocSurfaceTriangles( quads * 4, quads * 6 );
  282. tri->numIndexes = quads * 6;
  283. tri->numVerts = quads * 4;
  284. // just always draw the particles
  285. tri->bounds[0][0] =
  286. tri->bounds[0][1] =
  287. tri->bounds[0][2] = -99999;
  288. tri->bounds[1][0] =
  289. tri->bounds[1][1] =
  290. tri->bounds[1][2] = 99999;
  291. tri->numVerts = 0;
  292. for ( last = NULL, smoke = active->smokes; smoke; smoke = next ) {
  293. next = smoke->next;
  294. #ifdef _D3XP
  295. if ( smoke->timeGroup ) {
  296. g.frac = (float)( gameLocal.fast.time - smoke->privateStartTime ) / (stage->particleLife * 1000);
  297. }
  298. else {
  299. g.frac = (float)( gameLocal.time - smoke->privateStartTime ) / (stage->particleLife * 1000);
  300. }
  301. #else
  302. g.frac = (float)( gameLocal.time - smoke->privateStartTime ) / (stage->particleLife * 1000);
  303. #endif
  304. if ( g.frac >= 1.0f ) {
  305. // remove the particle from the stage list
  306. if ( last != NULL ) {
  307. last->next = smoke->next;
  308. } else {
  309. active->smokes = smoke->next;
  310. }
  311. // put the particle on the free list
  312. smoke->next = freeSmokes;
  313. freeSmokes = smoke;
  314. numActiveSmokes--;
  315. continue;
  316. }
  317. g.index = smoke->index;
  318. g.random = smoke->random;
  319. g.origin = smoke->origin;
  320. g.axis = smoke->axis;
  321. g.originalRandom = g.random;
  322. g.age = g.frac * stage->particleLife;
  323. tri->numVerts += stage->CreateParticle( &g, tri->verts + tri->numVerts );
  324. last = smoke;
  325. }
  326. if ( tri->numVerts > quads * 4 ) {
  327. gameLocal.Error( "idSmokeParticles::UpdateRenderEntity: miscounted verts" );
  328. }
  329. if ( tri->numVerts == 0 ) {
  330. // they were all removed
  331. renderEntity->hModel->FreeSurfaceTriangles( tri );
  332. if ( !active->smokes ) {
  333. // remove this from the activeStages list
  334. activeStages.RemoveIndex( activeStageNum );
  335. activeStageNum--;
  336. }
  337. } else {
  338. // build the index list
  339. int indexes = 0;
  340. for ( int i = 0 ; i < tri->numVerts ; i += 4 ) {
  341. tri->indexes[indexes+0] = i;
  342. tri->indexes[indexes+1] = i+2;
  343. tri->indexes[indexes+2] = i+3;
  344. tri->indexes[indexes+3] = i;
  345. tri->indexes[indexes+4] = i+3;
  346. tri->indexes[indexes+5] = i+1;
  347. indexes += 6;
  348. }
  349. tri->numIndexes = indexes;
  350. modelSurface_t surf;
  351. surf.geometry = tri;
  352. surf.shader = stage->material;
  353. surf.id = 0;
  354. renderEntity->hModel->AddSurface( surf );
  355. }
  356. }
  357. return true;
  358. }
  359. /*
  360. ================
  361. idSmokeParticles::ModelCallback
  362. ================
  363. */
  364. bool idSmokeParticles::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) {
  365. // update the particles
  366. if ( gameLocal.smokeParticles ) {
  367. return gameLocal.smokeParticles->UpdateRenderEntity( renderEntity, renderView );
  368. }
  369. return true;
  370. }