SmokeParticles.cpp 11 KB

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