SmokeParticles.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. /*
  2. ===========================================================================
  3. Doom 3 BFG Edition GPL Source Code
  4. Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
  5. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
  6. Doom 3 BFG Edition 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 BFG Edition 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 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
  16. In addition, the Doom 3 BFG Edition 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 BFG Edition 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() {
  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() {
  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() {
  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() {
  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. float frac;
  105. if ( smoke->timeGroup ) {
  106. frac = (float)( gameLocal.fast.time - smoke->privateStartTime ) / ( stage->particleLife * 1000 );
  107. }
  108. else {
  109. frac = (float)( gameLocal.slow.time - smoke->privateStartTime ) / ( stage->particleLife * 1000 );
  110. }
  111. if ( frac >= 1.0f ) {
  112. // remove the particle from the stage list
  113. if ( last != NULL ) {
  114. last->next = smoke->next;
  115. } else {
  116. active->smokes = smoke->next;
  117. }
  118. // put the particle on the free list
  119. smoke->next = freeSmokes;
  120. freeSmokes = smoke;
  121. numActiveSmokes--;
  122. continue;
  123. }
  124. last = smoke;
  125. }
  126. if ( !active->smokes ) {
  127. // remove this from the activeStages list
  128. activeStages.RemoveIndex( activeStageNum );
  129. activeStageNum--;
  130. }
  131. }
  132. }
  133. /*
  134. ================
  135. idSmokeParticles::EmitSmoke
  136. Called by game code to drop another particle into the list
  137. ================
  138. */
  139. bool idSmokeParticles::EmitSmoke( const idDeclParticle *smoke, const int systemStartTime, const float diversity, const idVec3 &origin, const idMat3 &axis, int timeGroup /*_D3XP*/ ) {
  140. bool continues = false;
  141. SetTimeState ts( timeGroup );
  142. if ( !smoke ) {
  143. return false;
  144. }
  145. if ( !gameLocal.isNewFrame ) {
  146. return false;
  147. }
  148. // dedicated doesn't smoke. No UpdateRenderEntity, so they would not be freed
  149. if ( gameLocal.GetLocalClientNum() < 0 ) {
  150. return false;
  151. }
  152. assert( gameLocal.time == 0 || systemStartTime <= gameLocal.time );
  153. if ( systemStartTime > gameLocal.time ) {
  154. return false;
  155. }
  156. idRandom steppingRandom( 0xffff * diversity );
  157. // for each stage in the smoke that is still emitting particles, emit a new singleSmoke_t
  158. for ( int stageNum = 0; stageNum < smoke->stages.Num(); stageNum++ ) {
  159. const idParticleStage *stage = smoke->stages[stageNum];
  160. if ( !stage->cycleMsec ) {
  161. continue;
  162. }
  163. if ( !stage->material ) {
  164. continue;
  165. }
  166. if ( stage->particleLife <= 0 ) {
  167. continue;
  168. }
  169. // see how many particles we should emit this tic
  170. // FIXME: smoke.privateStartTime += stage->timeOffset;
  171. int finalParticleTime = stage->cycleMsec * stage->spawnBunching;
  172. int deltaMsec = gameLocal.time - systemStartTime;
  173. int nowCount = 0, prevCount = 0;
  174. if ( finalParticleTime == 0 ) {
  175. // if spawnBunching is 0, they will all come out at once
  176. if ( gameLocal.time == systemStartTime ) {
  177. prevCount = -1;
  178. nowCount = stage->totalParticles-1;
  179. } else {
  180. prevCount = stage->totalParticles;
  181. }
  182. } else {
  183. nowCount = floor( ( (float)deltaMsec / finalParticleTime ) * stage->totalParticles );
  184. if ( nowCount >= stage->totalParticles ) {
  185. nowCount = stage->totalParticles-1;
  186. }
  187. prevCount = floor( ((float)( deltaMsec - ( gameLocal.time - gameLocal.previousTime ) ) / finalParticleTime) * stage->totalParticles );
  188. if ( prevCount < -1 ) {
  189. prevCount = -1;
  190. }
  191. }
  192. if ( prevCount >= stage->totalParticles ) {
  193. // no more particles from this stage
  194. continue;
  195. }
  196. if ( nowCount < stage->totalParticles-1 ) {
  197. // the system will need to emit particles next frame as well
  198. continues = true;
  199. }
  200. // find an activeSmokeStage that matches this
  201. activeSmokeStage_t *active = NULL;
  202. int i;
  203. for ( i = 0 ; i < activeStages.Num() ; i++ ) {
  204. active = &activeStages[i];
  205. if ( active->stage == stage ) {
  206. break;
  207. }
  208. }
  209. if ( i == activeStages.Num() ) {
  210. // add a new one
  211. activeSmokeStage_t newActive;
  212. newActive.smokes = NULL;
  213. newActive.stage = stage;
  214. i = activeStages.Append( newActive );
  215. active = &activeStages[i];
  216. }
  217. // add all the required particles
  218. for ( prevCount++ ; prevCount <= nowCount && active != NULL ; prevCount++ ) {
  219. if ( !freeSmokes ) {
  220. gameLocal.Printf( "idSmokeParticles::EmitSmoke: no free smokes with %d active stages\n", activeStages.Num() );
  221. return true;
  222. }
  223. singleSmoke_t *newSmoke = freeSmokes;
  224. freeSmokes = freeSmokes->next;
  225. numActiveSmokes++;
  226. newSmoke->timeGroup = timeGroup;
  227. newSmoke->index = prevCount;
  228. newSmoke->axis = axis;
  229. newSmoke->origin = origin;
  230. newSmoke->random = steppingRandom;
  231. newSmoke->privateStartTime = systemStartTime + prevCount * finalParticleTime / stage->totalParticles;
  232. newSmoke->next = active->smokes;
  233. active->smokes = newSmoke;
  234. steppingRandom.RandomInt(); // advance the random
  235. }
  236. }
  237. return continues;
  238. }
  239. /*
  240. ================
  241. idSmokeParticles::UpdateRenderEntity
  242. ================
  243. */
  244. bool idSmokeParticles::UpdateRenderEntity( renderEntity_s *renderEntity, const renderView_t *renderView ) {
  245. // this may be triggered by a model trace or other non-view related source,
  246. // to which we should look like an empty model
  247. if ( !renderView ) {
  248. // FIXME: re-use model surfaces
  249. renderEntity->hModel->InitEmpty( smokeParticle_SnapshotName );
  250. return false;
  251. }
  252. // don't regenerate it if it is current
  253. if ( renderView->time[renderEntity->timeGroup] == currentParticleTime && !renderView->forceUpdate ) {
  254. return false;
  255. }
  256. // FIXME: re-use model surfaces
  257. renderEntity->hModel->InitEmpty( smokeParticle_SnapshotName );
  258. currentParticleTime = renderView->time[renderEntity->timeGroup];
  259. particleGen_t g;
  260. g.renderEnt = renderEntity;
  261. g.renderView = renderView;
  262. for ( int activeStageNum = 0; activeStageNum < activeStages.Num(); activeStageNum++ ) {
  263. singleSmoke_t *smoke, *next, *last;
  264. activeSmokeStage_t *active = &activeStages[activeStageNum];
  265. const idParticleStage *stage = active->stage;
  266. if ( !stage->material ) {
  267. continue;
  268. }
  269. // allocate a srfTriangles that can hold all the particles
  270. int count = 0;
  271. for ( smoke = active->smokes; smoke; smoke = smoke->next ) {
  272. count++;
  273. }
  274. int quads = count * stage->NumQuadsPerParticle();
  275. srfTriangles_t *tri = renderEntity->hModel->AllocSurfaceTriangles( quads * 4, quads * 6 );
  276. tri->numIndexes = quads * 6;
  277. tri->numVerts = quads * 4;
  278. // just always draw the particles
  279. tri->bounds[0][0] =
  280. tri->bounds[0][1] =
  281. tri->bounds[0][2] = -99999;
  282. tri->bounds[1][0] =
  283. tri->bounds[1][1] =
  284. tri->bounds[1][2] = 99999;
  285. tri->numVerts = 0;
  286. for ( last = NULL, smoke = active->smokes; smoke; smoke = next ) {
  287. next = smoke->next;
  288. if ( smoke->timeGroup ) {
  289. g.frac = (float)( gameLocal.fast.time - smoke->privateStartTime ) / (stage->particleLife * 1000);
  290. }
  291. else {
  292. g.frac = (float)( gameLocal.time - smoke->privateStartTime ) / (stage->particleLife * 1000);
  293. }
  294. if ( g.frac >= 1.0f ) {
  295. // remove the particle from the stage list
  296. if ( last != NULL ) {
  297. last->next = smoke->next;
  298. } else {
  299. active->smokes = smoke->next;
  300. }
  301. // put the particle on the free list
  302. smoke->next = freeSmokes;
  303. freeSmokes = smoke;
  304. numActiveSmokes--;
  305. continue;
  306. }
  307. g.index = smoke->index;
  308. g.random = smoke->random;
  309. g.origin = smoke->origin;
  310. g.axis = smoke->axis;
  311. g.originalRandom = g.random;
  312. g.age = g.frac * stage->particleLife;
  313. tri->numVerts += stage->CreateParticle( &g, tri->verts + tri->numVerts );
  314. last = smoke;
  315. }
  316. if ( tri->numVerts > quads * 4 ) {
  317. gameLocal.Error( "idSmokeParticles::UpdateRenderEntity: miscounted verts" );
  318. }
  319. if ( tri->numVerts == 0 ) {
  320. // they were all removed
  321. renderEntity->hModel->FreeSurfaceTriangles( tri );
  322. if ( !active->smokes ) {
  323. // remove this from the activeStages list
  324. activeStages.RemoveIndex( activeStageNum );
  325. activeStageNum--;
  326. }
  327. } else {
  328. // build the index list
  329. int indexes = 0;
  330. for ( int i = 0 ; i < tri->numVerts ; i += 4 ) {
  331. tri->indexes[indexes+0] = i;
  332. tri->indexes[indexes+1] = i+2;
  333. tri->indexes[indexes+2] = i+3;
  334. tri->indexes[indexes+3] = i;
  335. tri->indexes[indexes+4] = i+3;
  336. tri->indexes[indexes+5] = i+1;
  337. indexes += 6;
  338. }
  339. tri->numIndexes = indexes;
  340. modelSurface_t surf;
  341. surf.geometry = tri;
  342. surf.shader = stage->material;
  343. surf.id = 0;
  344. renderEntity->hModel->AddSurface( surf );
  345. }
  346. }
  347. return true;
  348. }
  349. /*
  350. ================
  351. idSmokeParticles::ModelCallback
  352. ================
  353. */
  354. bool idSmokeParticles::ModelCallback( renderEntity_s *renderEntity, const renderView_t *renderView ) {
  355. // update the particles
  356. if ( gameLocal.smokeParticles ) {
  357. return gameLocal.smokeParticles->UpdateRenderEntity( renderEntity, renderView );
  358. }
  359. return true;
  360. }