DeclParticle.cpp 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643
  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. idCVar binaryLoadParticles( "binaryLoadParticles", "1", 0, "enable binary load/write of particle decls" );
  23. static const byte BPRT_VERSION = 101;
  24. static const unsigned int BPRT_MAGIC = ( 'B' << 24 ) | ( 'P' << 16 ) | ( 'R' << 8 ) | BPRT_VERSION;
  25. struct ParticleParmDesc {
  26. const char *name;
  27. int count;
  28. const char *desc;
  29. };
  30. const ParticleParmDesc ParticleDistributionDesc[] = {
  31. { "rect", 3, "" },
  32. { "cylinder", 4, "" },
  33. { "sphere", 3, "" }
  34. };
  35. const ParticleParmDesc ParticleDirectionDesc[] = {
  36. { "cone", 1, "" },
  37. { "outward", 1, "" },
  38. };
  39. const ParticleParmDesc ParticleOrientationDesc[] = {
  40. { "view", 0, "" },
  41. { "aimed", 2, "" },
  42. { "x", 0, "" },
  43. { "y", 0, "" },
  44. { "z", 0, "" }
  45. };
  46. const ParticleParmDesc ParticleCustomDesc[] = {
  47. { "standard", 0, "Standard" },
  48. { "helix", 5, "sizeX Y Z radialSpeed axialSpeed" },
  49. { "flies", 3, "radialSpeed axialSpeed size" },
  50. { "orbit", 2, "radius speed"},
  51. { "drip", 2, "something something" }
  52. };
  53. const int CustomParticleCount = sizeof( ParticleCustomDesc ) / sizeof( const ParticleParmDesc );
  54. /*
  55. =================
  56. idDeclParticle::Size
  57. =================
  58. */
  59. size_t idDeclParticle::Size() const {
  60. return sizeof( idDeclParticle );
  61. }
  62. /*
  63. =====================
  64. idDeclParticle::GetStageBounds
  65. =====================
  66. */
  67. void idDeclParticle::GetStageBounds( idParticleStage *stage ) {
  68. stage->bounds.Clear();
  69. // this isn't absolutely guaranteed, but it should be close
  70. particleGen_t g;
  71. renderEntity_t renderEntity;
  72. memset( &renderEntity, 0, sizeof( renderEntity ) );
  73. renderEntity.axis = mat3_identity;
  74. renderView_t renderView;
  75. memset( &renderView, 0, sizeof( renderView ) );
  76. renderView.viewaxis = mat3_identity;
  77. g.renderEnt = &renderEntity;
  78. g.renderView = &renderView;
  79. g.origin.Zero();
  80. g.axis = mat3_identity;
  81. idRandom steppingRandom;
  82. steppingRandom.SetSeed( 0 );
  83. // just step through a lot of possible particles as a representative sampling
  84. for ( int i = 0 ; i < 1000 ; i++ ) {
  85. g.random = g.originalRandom = steppingRandom;
  86. int maxMsec = stage->particleLife * 1000;
  87. for ( int inCycleTime = 0 ; inCycleTime < maxMsec ; inCycleTime += 16 ) {
  88. // make sure we get the very last tic, which may make up an extreme edge
  89. if ( inCycleTime + 16 > maxMsec ) {
  90. inCycleTime = maxMsec - 1;
  91. }
  92. g.frac = (float)inCycleTime / ( stage->particleLife * 1000 );
  93. g.age = inCycleTime * 0.001f;
  94. // if the particle doesn't get drawn because it is faded out or beyond a kill region,
  95. // don't increment the verts
  96. idVec3 origin;
  97. stage->ParticleOrigin( &g, origin );
  98. stage->bounds.AddPoint( origin );
  99. }
  100. }
  101. // find the max size
  102. float maxSize = 0;
  103. for ( float f = 0; f <= 1.0f; f += 1.0f / 64 ) {
  104. float size = stage->size.Eval( f, steppingRandom );
  105. float aspect = stage->aspect.Eval( f, steppingRandom );
  106. if ( aspect > 1 ) {
  107. size *= aspect;
  108. }
  109. if ( size > maxSize ) {
  110. maxSize = size;
  111. }
  112. }
  113. maxSize += 8; // just for good measure
  114. // users can specify a per-stage bounds expansion to handle odd cases
  115. stage->bounds.ExpandSelf( maxSize + stage->boundsExpansion );
  116. }
  117. /*
  118. ================
  119. idDeclParticle::ParseParms
  120. Parses a variable length list of parms on one line
  121. ================
  122. */
  123. void idDeclParticle::ParseParms( idLexer &src, float *parms, int maxParms ) {
  124. idToken token;
  125. memset( parms, 0, maxParms * sizeof( *parms ) );
  126. int count = 0;
  127. while( 1 ) {
  128. if ( !src.ReadTokenOnLine( &token ) ) {
  129. return;
  130. }
  131. if ( count == maxParms ) {
  132. src.Error( "too many parms on line" );
  133. return;
  134. }
  135. token.StripQuotes();
  136. parms[count] = atof( token );
  137. count++;
  138. }
  139. }
  140. /*
  141. ================
  142. idDeclParticle::ParseParametric
  143. ================
  144. */
  145. void idDeclParticle::ParseParametric( idLexer &src, idParticleParm *parm ) {
  146. idToken token;
  147. parm->table = NULL;
  148. parm->from = parm->to = 0.0f;
  149. if ( !src.ReadToken( &token ) ) {
  150. src.Error( "not enough parameters" );
  151. return;
  152. }
  153. if ( token.IsNumeric() ) {
  154. // can have a to + 2nd parm
  155. parm->from = parm->to = atof( token );
  156. if ( src.ReadToken( &token ) ) {
  157. if ( !token.Icmp( "to" ) ) {
  158. if ( !src.ReadToken( &token ) ) {
  159. src.Error( "missing second parameter" );
  160. return;
  161. }
  162. parm->to = atof( token );
  163. } else {
  164. src.UnreadToken( &token );
  165. }
  166. }
  167. } else {
  168. // table
  169. parm->table = static_cast<const idDeclTable *>( declManager->FindType( DECL_TABLE, token, false ) );
  170. }
  171. }
  172. /*
  173. ================
  174. idDeclParticle::ParseParticleStage
  175. ================
  176. */
  177. idParticleStage *idDeclParticle::ParseParticleStage( idLexer &src ) {
  178. idToken token;
  179. idParticleStage *stage = new (TAG_DECL) idParticleStage;
  180. stage->Default();
  181. while (1) {
  182. if ( src.HadError() ) {
  183. break;
  184. }
  185. if ( !src.ReadToken( &token ) ) {
  186. break;
  187. }
  188. if ( !token.Icmp( "}" ) ) {
  189. break;
  190. }
  191. if ( !token.Icmp( "material" ) ) {
  192. src.ReadToken( &token );
  193. stage->material = declManager->FindMaterial( token.c_str() );
  194. continue;
  195. }
  196. if ( !token.Icmp( "count" ) ) {
  197. stage->totalParticles = src.ParseInt();
  198. continue;
  199. }
  200. if ( !token.Icmp( "time" ) ) {
  201. stage->particleLife = src.ParseFloat();
  202. continue;
  203. }
  204. if ( !token.Icmp( "cycles" ) ) {
  205. stage->cycles = src.ParseFloat();
  206. continue;
  207. }
  208. if ( !token.Icmp( "timeOffset" ) ) {
  209. stage->timeOffset = src.ParseFloat();
  210. continue;
  211. }
  212. if ( !token.Icmp( "deadTime" ) ) {
  213. stage->deadTime = src.ParseFloat();
  214. continue;
  215. }
  216. if ( !token.Icmp( "randomDistribution" ) ) {
  217. stage->randomDistribution = src.ParseBool();
  218. continue;
  219. }
  220. if ( !token.Icmp( "bunching" ) ) {
  221. stage->spawnBunching = src.ParseFloat();
  222. continue;
  223. }
  224. if ( !token.Icmp( "distribution" ) ) {
  225. src.ReadToken( &token );
  226. if ( !token.Icmp( "rect" ) ) {
  227. stage->distributionType = PDIST_RECT;
  228. } else if ( !token.Icmp( "cylinder" ) ) {
  229. stage->distributionType = PDIST_CYLINDER;
  230. } else if ( !token.Icmp( "sphere" ) ) {
  231. stage->distributionType = PDIST_SPHERE;
  232. } else {
  233. src.Error( "bad distribution type: %s\n", token.c_str() );
  234. }
  235. ParseParms( src, stage->distributionParms, sizeof( stage->distributionParms ) / sizeof( stage->distributionParms[0] ) );
  236. continue;
  237. }
  238. if ( !token.Icmp( "direction" ) ) {
  239. src.ReadToken( &token );
  240. if ( !token.Icmp( "cone" ) ) {
  241. stage->directionType = PDIR_CONE;
  242. } else if ( !token.Icmp( "outward" ) ) {
  243. stage->directionType = PDIR_OUTWARD;
  244. } else {
  245. src.Error( "bad direction type: %s\n", token.c_str() );
  246. }
  247. ParseParms( src, stage->directionParms, sizeof( stage->directionParms ) / sizeof( stage->directionParms[0] ) );
  248. continue;
  249. }
  250. if ( !token.Icmp( "orientation" ) ) {
  251. src.ReadToken( &token );
  252. if ( !token.Icmp( "view" ) ) {
  253. stage->orientation = POR_VIEW;
  254. } else if ( !token.Icmp( "aimed" ) ) {
  255. stage->orientation = POR_AIMED;
  256. } else if ( !token.Icmp( "x" ) ) {
  257. stage->orientation = POR_X;
  258. } else if ( !token.Icmp( "y" ) ) {
  259. stage->orientation = POR_Y;
  260. } else if ( !token.Icmp( "z" ) ) {
  261. stage->orientation = POR_Z;
  262. } else {
  263. src.Error( "bad orientation type: %s\n", token.c_str() );
  264. }
  265. ParseParms( src, stage->orientationParms, sizeof( stage->orientationParms ) / sizeof( stage->orientationParms[0] ) );
  266. continue;
  267. }
  268. if ( !token.Icmp( "customPath" ) ) {
  269. src.ReadToken( &token );
  270. if ( !token.Icmp( "standard" ) ) {
  271. stage->customPathType = PPATH_STANDARD;
  272. } else if ( !token.Icmp( "helix" ) ) {
  273. stage->customPathType = PPATH_HELIX;
  274. } else if ( !token.Icmp( "flies" ) ) {
  275. stage->customPathType = PPATH_FLIES;
  276. } else if ( !token.Icmp( "spherical" ) ) {
  277. stage->customPathType = PPATH_ORBIT;
  278. } else {
  279. src.Error( "bad path type: %s\n", token.c_str() );
  280. }
  281. ParseParms( src, stage->customPathParms, sizeof( stage->customPathParms ) / sizeof( stage->customPathParms[0] ) );
  282. continue;
  283. }
  284. if ( !token.Icmp( "speed" ) ) {
  285. ParseParametric( src, &stage->speed );
  286. continue;
  287. }
  288. if ( !token.Icmp( "rotation" ) ) {
  289. ParseParametric( src, &stage->rotationSpeed );
  290. continue;
  291. }
  292. if ( !token.Icmp( "angle" ) ) {
  293. stage->initialAngle = src.ParseFloat();
  294. continue;
  295. }
  296. if ( !token.Icmp( "entityColor" ) ) {
  297. stage->entityColor = src.ParseBool();
  298. continue;
  299. }
  300. if ( !token.Icmp( "size" ) ) {
  301. ParseParametric( src, &stage->size );
  302. continue;
  303. }
  304. if ( !token.Icmp( "aspect" ) ) {
  305. ParseParametric( src, &stage->aspect );
  306. continue;
  307. }
  308. if ( !token.Icmp( "fadeIn" ) ) {
  309. stage->fadeInFraction = src.ParseFloat();
  310. continue;
  311. }
  312. if ( !token.Icmp( "fadeOut" ) ) {
  313. stage->fadeOutFraction = src.ParseFloat();
  314. continue;
  315. }
  316. if ( !token.Icmp( "fadeIndex" ) ) {
  317. stage->fadeIndexFraction = src.ParseFloat();
  318. continue;
  319. }
  320. if ( !token.Icmp( "color" ) ) {
  321. stage->color[0] = src.ParseFloat();
  322. stage->color[1] = src.ParseFloat();
  323. stage->color[2] = src.ParseFloat();
  324. stage->color[3] = src.ParseFloat();
  325. continue;
  326. }
  327. if ( !token.Icmp( "fadeColor" ) ) {
  328. stage->fadeColor[0] = src.ParseFloat();
  329. stage->fadeColor[1] = src.ParseFloat();
  330. stage->fadeColor[2] = src.ParseFloat();
  331. stage->fadeColor[3] = src.ParseFloat();
  332. continue;
  333. }
  334. if ( !token.Icmp("offset" ) ) {
  335. stage->offset[0] = src.ParseFloat();
  336. stage->offset[1] = src.ParseFloat();
  337. stage->offset[2] = src.ParseFloat();
  338. continue;
  339. }
  340. if ( !token.Icmp( "animationFrames" ) ) {
  341. stage->animationFrames = src.ParseInt();
  342. continue;
  343. }
  344. if ( !token.Icmp( "animationRate" ) ) {
  345. stage->animationRate = src.ParseFloat();
  346. continue;
  347. }
  348. if ( !token.Icmp( "boundsExpansion" ) ) {
  349. stage->boundsExpansion = src.ParseFloat();
  350. continue;
  351. }
  352. if ( !token.Icmp( "gravity" ) ) {
  353. src.ReadToken( &token );
  354. if ( !token.Icmp( "world" ) ) {
  355. stage->worldGravity = true;
  356. } else {
  357. src.UnreadToken( &token );
  358. }
  359. stage->gravity = src.ParseFloat();
  360. continue;
  361. }
  362. src.Error( "unknown token %s\n", token.c_str() );
  363. }
  364. // derive values
  365. stage->cycleMsec = ( stage->particleLife + stage->deadTime ) * 1000;
  366. return stage;
  367. }
  368. /*
  369. ================
  370. idDeclParticle::Parse
  371. ================
  372. */
  373. bool idDeclParticle::Parse( const char *text, const int textLength, bool allowBinaryVersion ) {
  374. if ( cvarSystem->GetCVarBool( "fs_buildresources" ) ) {
  375. fileSystem->AddParticlePreload( GetName() );
  376. }
  377. idLexer src;
  378. idToken token;
  379. unsigned int sourceChecksum = 0;
  380. idStrStatic< MAX_OSPATH > generatedFileName;
  381. if ( allowBinaryVersion ) {
  382. // Try to load the generated version of it
  383. // If successful,
  384. // - Create an MD5 of the hash of the source
  385. // - Load the MD5 of the generated, if they differ, create a new generated
  386. generatedFileName = "generated/particles/";
  387. generatedFileName.AppendPath( GetName() );
  388. generatedFileName.SetFileExtension( ".bprt" );
  389. idFileLocal file( fileSystem->OpenFileReadMemory( generatedFileName ) );
  390. sourceChecksum = MD5_BlockChecksum( text, textLength );
  391. if ( binaryLoadParticles.GetBool() && LoadBinary( file, sourceChecksum ) ) {
  392. return true;
  393. }
  394. }
  395. src.LoadMemory( text, textLength, GetFileName(), GetLineNum() );
  396. src.SetFlags( DECL_LEXER_FLAGS );
  397. src.SkipUntilString( "{" );
  398. depthHack = 0.0f;
  399. while (1) {
  400. if ( !src.ReadToken( &token ) ) {
  401. break;
  402. }
  403. if ( !token.Icmp( "}" ) ) {
  404. break;
  405. }
  406. if ( !token.Icmp( "{" ) ) {
  407. if ( stages.Num() >= MAX_PARTICLE_STAGES ) {
  408. src.Error( "Too many particle stages" );
  409. MakeDefault();
  410. return false;
  411. }
  412. idParticleStage *stage = ParseParticleStage( src );
  413. if ( !stage ) {
  414. src.Warning( "Particle stage parse failed" );
  415. MakeDefault();
  416. return false;
  417. }
  418. stages.Append( stage );
  419. continue;
  420. }
  421. if ( !token.Icmp( "depthHack" ) ) {
  422. depthHack = src.ParseFloat();
  423. continue;
  424. }
  425. src.Warning( "bad token %s", token.c_str() );
  426. MakeDefault();
  427. return false;
  428. }
  429. // don't calculate bounds or write binary files for defaulted ( non-existent ) particles in resource builds
  430. if ( fileSystem->UsingResourceFiles() ) {
  431. bounds = idBounds( vec3_origin ).Expand( 8.0f );
  432. return true;
  433. }
  434. //
  435. // calculate the bounds
  436. //
  437. bounds.Clear();
  438. for( int i = 0; i < stages.Num(); i++ ) {
  439. GetStageBounds( stages[i] );
  440. bounds.AddBounds( stages[i]->bounds );
  441. }
  442. if ( bounds.GetVolume() <= 0.1f ) {
  443. bounds = idBounds( vec3_origin ).Expand( 8.0f );
  444. }
  445. if ( allowBinaryVersion && binaryLoadParticles.GetBool() ) {
  446. idLib::Printf( "Writing %s\n", generatedFileName.c_str() );
  447. idFileLocal outputFile( fileSystem->OpenFileWrite( generatedFileName, "fs_basepath" ) );
  448. WriteBinary( outputFile, sourceChecksum );
  449. }
  450. return true;
  451. }
  452. /*
  453. ========================
  454. idDeclParticle::LoadBinary
  455. ========================
  456. */
  457. bool idDeclParticle::LoadBinary( idFile * file, unsigned int checksum ) {
  458. if ( file == NULL ) {
  459. return false;
  460. }
  461. struct local {
  462. static void LoadParticleParm( idFile * file, idParticleParm & parm ) {
  463. idStr name;
  464. file->ReadString( name );
  465. if ( name.IsEmpty() ) {
  466. parm.table = NULL;
  467. } else {
  468. parm.table = (idDeclTable *)declManager->FindType( DECL_TABLE, name, false );
  469. }
  470. file->ReadFloat( parm.from );
  471. file->ReadFloat( parm.to );
  472. }
  473. };
  474. unsigned int magic = 0;
  475. file->ReadBig( magic );
  476. if ( magic != BPRT_MAGIC ) {
  477. return false;
  478. }
  479. unsigned int loadedChecksum;
  480. file->ReadBig( loadedChecksum );
  481. if ( checksum != loadedChecksum && !fileSystem->InProductionMode() ) {
  482. return false;
  483. }
  484. int numStages;
  485. file->ReadBig( numStages );
  486. for ( int i = 0; i < numStages; i++ ) {
  487. idParticleStage * s = new (TAG_DECL) idParticleStage;
  488. stages.Append( s );
  489. assert( stages.Num() <= MAX_PARTICLE_STAGES );
  490. idStr name;
  491. file->ReadString( name );
  492. if ( name.IsEmpty() ) {
  493. s->material = NULL;
  494. } else {
  495. s->material = declManager->FindMaterial( name );
  496. }
  497. file->ReadBig( s->totalParticles );
  498. file->ReadFloat( s->cycles );
  499. file->ReadBig( s->cycleMsec );
  500. file->ReadFloat( s->spawnBunching );
  501. file->ReadFloat( s->particleLife );
  502. file->ReadFloat( s->timeOffset );
  503. file->ReadFloat( s->deadTime );
  504. file->ReadBig( s->distributionType );
  505. file->ReadBigArray( s->distributionParms, sizeof( s->distributionParms ) / sizeof ( s->distributionParms[0] ) );
  506. file->ReadBig( s->directionType );
  507. file->ReadBigArray( s->directionParms, sizeof( s->directionParms ) / sizeof ( s->directionParms[0] ) );
  508. local::LoadParticleParm( file, s->speed );
  509. file->ReadFloat( s->gravity );
  510. file->ReadBig( s->worldGravity );
  511. file->ReadBig( s->randomDistribution );
  512. file->ReadBig( s->entityColor );
  513. file->ReadBig( s->customPathType );
  514. file->ReadBigArray( s->customPathParms, sizeof( s->customPathParms ) / sizeof ( s->customPathParms[0] ) );
  515. file->ReadVec3( s->offset );
  516. file->ReadBig( s->animationFrames );
  517. file->ReadFloat( s->animationRate );
  518. file->ReadFloat( s->initialAngle );
  519. local::LoadParticleParm( file, s->rotationSpeed );
  520. file->ReadBig( s->orientation );
  521. file->ReadBigArray( s->orientationParms, sizeof( s->orientationParms ) / sizeof ( s->orientationParms[0] ) );
  522. local::LoadParticleParm( file, s->size );
  523. local::LoadParticleParm( file, s->aspect );
  524. file->ReadVec4( s->color );
  525. file->ReadVec4( s->fadeColor );
  526. file->ReadFloat( s->fadeInFraction );
  527. file->ReadFloat( s->fadeOutFraction );
  528. file->ReadFloat( s->fadeIndexFraction );
  529. file->ReadBig( s->hidden );
  530. file->ReadFloat( s->boundsExpansion );
  531. file->ReadVec3( s->bounds[0] );
  532. file->ReadVec3( s->bounds[1] );
  533. }
  534. file->ReadVec3( bounds[0] );
  535. file->ReadVec3( bounds[1] );
  536. file->ReadFloat( depthHack );
  537. return true;
  538. }
  539. /*
  540. ========================
  541. idDeclParticle::WriteBinary
  542. ========================
  543. */
  544. void idDeclParticle::WriteBinary( idFile * file, unsigned int checksum ) {
  545. if ( file == NULL ) {
  546. return;
  547. }
  548. struct local {
  549. static void WriteParticleParm( idFile * file, idParticleParm & parm ) {
  550. if ( parm.table != NULL && parm.table->GetName() != NULL ) {
  551. file->WriteString( parm.table->GetName() );
  552. } else {
  553. file->WriteString( "" );
  554. }
  555. file->WriteFloat( parm.from );
  556. file->WriteFloat( parm.to );
  557. }
  558. };
  559. file->WriteBig( BPRT_MAGIC );
  560. file->WriteBig( checksum );
  561. file->WriteBig( stages.Num() );
  562. for ( int i = 0; i < stages.Num(); i++ ) {
  563. idParticleStage * s = stages[i];
  564. if ( s->material != NULL && s->material->GetName() != NULL ) {
  565. file->WriteString( s->material->GetName() );
  566. } else {
  567. file->WriteString( "" );
  568. }
  569. file->WriteBig( s->totalParticles );
  570. file->WriteFloat( s->cycles );
  571. file->WriteBig( s->cycleMsec );
  572. file->WriteFloat( s->spawnBunching );
  573. file->WriteFloat( s->particleLife );
  574. file->WriteFloat( s->timeOffset );
  575. file->WriteFloat( s->deadTime );
  576. file->WriteBig( s->distributionType );
  577. file->WriteBigArray( s->distributionParms, sizeof( s->distributionParms ) / sizeof ( s->distributionParms[0] ) );
  578. file->WriteBig( s->directionType );
  579. file->WriteBigArray( s->directionParms, sizeof( s->directionParms ) / sizeof ( s->directionParms[0] ) );
  580. local::WriteParticleParm( file, s->speed );
  581. file->WriteFloat( s->gravity );
  582. file->WriteBig( s->worldGravity );
  583. file->WriteBig( s->randomDistribution );
  584. file->WriteBig( s->entityColor );
  585. file->WriteBig( s->customPathType );
  586. file->WriteBigArray( s->customPathParms, sizeof( s->customPathParms ) / sizeof ( s->customPathParms[0] ) );
  587. file->WriteVec3( s->offset );
  588. file->WriteBig( s->animationFrames );
  589. file->WriteFloat( s->animationRate );
  590. file->WriteFloat( s->initialAngle );
  591. local::WriteParticleParm( file, s->rotationSpeed );
  592. file->WriteBig( s->orientation );
  593. file->WriteBigArray( s->orientationParms, sizeof( s->orientationParms ) / sizeof ( s->orientationParms[0] ) );
  594. local::WriteParticleParm( file, s->size );
  595. local::WriteParticleParm( file, s->aspect );
  596. file->WriteVec4( s->color );
  597. file->WriteVec4( s->fadeColor );
  598. file->WriteFloat( s->fadeInFraction );
  599. file->WriteFloat( s->fadeOutFraction );
  600. file->WriteFloat( s->fadeIndexFraction );
  601. file->WriteBig( s->hidden );
  602. file->WriteFloat( s->boundsExpansion );
  603. file->WriteVec3( s->bounds[0] );
  604. file->WriteVec3( s->bounds[1] );
  605. }
  606. file->WriteVec3( bounds[0] );
  607. file->WriteVec3( bounds[1] );
  608. file->WriteFloat( depthHack );
  609. }
  610. /*
  611. ================
  612. idDeclParticle::FreeData
  613. ================
  614. */
  615. void idDeclParticle::FreeData() {
  616. stages.DeleteContents( true );
  617. }
  618. /*
  619. ================
  620. idDeclParticle::DefaultDefinition
  621. ================
  622. */
  623. const char *idDeclParticle::DefaultDefinition() const {
  624. return
  625. "{\n"
  626. "\t" "{\n"
  627. "\t\t" "material\t_default\n"
  628. "\t\t" "count\t20\n"
  629. "\t\t" "time\t\t1.0\n"
  630. "\t" "}\n"
  631. "}";
  632. }
  633. /*
  634. ================
  635. idDeclParticle::WriteParticleParm
  636. ================
  637. */
  638. void idDeclParticle::WriteParticleParm( idFile *f, idParticleParm *parm, const char *name ) {
  639. f->WriteFloatString( "\t\t%s\t\t\t\t ", name );
  640. if ( parm->table ) {
  641. f->WriteFloatString( "%s\n", parm->table->GetName() );
  642. } else {
  643. f->WriteFloatString( "\"%.3f\" ", parm->from );
  644. if ( parm->from == parm->to ) {
  645. f->WriteFloatString( "\n" );
  646. } else {
  647. f->WriteFloatString( " to \"%.3f\"\n", parm->to );
  648. }
  649. }
  650. }
  651. /*
  652. ================
  653. idDeclParticle::WriteStage
  654. ================
  655. */
  656. void idDeclParticle::WriteStage( idFile *f, idParticleStage *stage ) {
  657. int i;
  658. f->WriteFloatString( "\t{\n" );
  659. f->WriteFloatString( "\t\tcount\t\t\t\t%i\n", stage->totalParticles );
  660. f->WriteFloatString( "\t\tmaterial\t\t\t%s\n", stage->material->GetName() );
  661. if ( stage->animationFrames ) {
  662. f->WriteFloatString( "\t\tanimationFrames \t%i\n", stage->animationFrames );
  663. }
  664. if ( stage->animationRate ) {
  665. f->WriteFloatString( "\t\tanimationRate \t\t%.3f\n", stage->animationRate );
  666. }
  667. f->WriteFloatString( "\t\ttime\t\t\t\t%.3f\n", stage->particleLife );
  668. f->WriteFloatString( "\t\tcycles\t\t\t\t%.3f\n", stage->cycles );
  669. if ( stage->timeOffset ) {
  670. f->WriteFloatString( "\t\ttimeOffset\t\t\t%.3f\n", stage->timeOffset );
  671. }
  672. if ( stage->deadTime ) {
  673. f->WriteFloatString( "\t\tdeadTime\t\t\t%.3f\n", stage->deadTime );
  674. }
  675. f->WriteFloatString( "\t\tbunching\t\t\t%.3f\n", stage->spawnBunching );
  676. f->WriteFloatString( "\t\tdistribution\t\t%s ", ParticleDistributionDesc[stage->distributionType].name );
  677. for ( i = 0; i < ParticleDistributionDesc[stage->distributionType].count; i++ ) {
  678. f->WriteFloatString( "%.3f ", stage->distributionParms[i] );
  679. }
  680. f->WriteFloatString( "\n" );
  681. f->WriteFloatString( "\t\tdirection\t\t\t%s ", ParticleDirectionDesc[stage->directionType].name );
  682. for ( i = 0; i < ParticleDirectionDesc[stage->directionType].count; i++ ) {
  683. f->WriteFloatString( "\"%.3f\" ", stage->directionParms[i] );
  684. }
  685. f->WriteFloatString( "\n" );
  686. f->WriteFloatString( "\t\torientation\t\t\t%s ", ParticleOrientationDesc[stage->orientation].name );
  687. for ( i = 0; i < ParticleOrientationDesc[stage->orientation].count; i++ ) {
  688. f->WriteFloatString( "%.3f ", stage->orientationParms[i] );
  689. }
  690. f->WriteFloatString( "\n" );
  691. if ( stage->customPathType != PPATH_STANDARD ) {
  692. f->WriteFloatString( "\t\tcustomPath %s ", ParticleCustomDesc[stage->customPathType].name );
  693. for ( i = 0; i < ParticleCustomDesc[stage->customPathType].count; i++ ) {
  694. f->WriteFloatString( "%.3f ", stage->customPathParms[i] );
  695. }
  696. f->WriteFloatString( "\n" );
  697. }
  698. if ( stage->entityColor ) {
  699. f->WriteFloatString( "\t\tentityColor\t\t\t1\n" );
  700. }
  701. WriteParticleParm( f, &stage->speed, "speed" );
  702. WriteParticleParm( f, &stage->size, "size" );
  703. WriteParticleParm( f, &stage->aspect, "aspect" );
  704. if ( stage->rotationSpeed.from ) {
  705. WriteParticleParm( f, &stage->rotationSpeed, "rotation" );
  706. }
  707. if ( stage->initialAngle ) {
  708. f->WriteFloatString( "\t\tangle\t\t\t\t%.3f\n", stage->initialAngle );
  709. }
  710. f->WriteFloatString( "\t\trandomDistribution\t\t\t\t%i\n", static_cast<int>( stage->randomDistribution ) );
  711. f->WriteFloatString( "\t\tboundsExpansion\t\t\t\t%.3f\n", stage->boundsExpansion );
  712. f->WriteFloatString( "\t\tfadeIn\t\t\t\t%.3f\n", stage->fadeInFraction );
  713. f->WriteFloatString( "\t\tfadeOut\t\t\t\t%.3f\n", stage->fadeOutFraction );
  714. f->WriteFloatString( "\t\tfadeIndex\t\t\t\t%.3f\n", stage->fadeIndexFraction );
  715. f->WriteFloatString( "\t\tcolor \t\t\t\t%.3f %.3f %.3f %.3f\n", stage->color.x, stage->color.y, stage->color.z, stage->color.w );
  716. f->WriteFloatString( "\t\tfadeColor \t\t\t%.3f %.3f %.3f %.3f\n", stage->fadeColor.x, stage->fadeColor.y, stage->fadeColor.z, stage->fadeColor.w );
  717. f->WriteFloatString( "\t\toffset \t\t\t\t%.3f %.3f %.3f\n", stage->offset.x, stage->offset.y, stage->offset.z );
  718. f->WriteFloatString( "\t\tgravity \t\t\t" );
  719. if ( stage->worldGravity ) {
  720. f->WriteFloatString( "world " );
  721. }
  722. f->WriteFloatString( "%.3f\n", stage->gravity );
  723. f->WriteFloatString( "\t}\n" );
  724. }
  725. /*
  726. ================
  727. idDeclParticle::RebuildTextSource
  728. ================
  729. */
  730. bool idDeclParticle::RebuildTextSource() {
  731. idFile_Memory f;
  732. f.WriteFloatString("\n\n/*\n"
  733. "\tGenerated by the Particle Editor.\n"
  734. "\tTo use the particle editor, launch the game and type 'editParticles' on the console.\n"
  735. "*/\n" );
  736. f.WriteFloatString( "particle %s {\n", GetName() );
  737. if ( depthHack ) {
  738. f.WriteFloatString( "\tdepthHack\t%f\n", depthHack );
  739. }
  740. for ( int i = 0; i < stages.Num(); i++ ) {
  741. WriteStage( &f, stages[i] );
  742. }
  743. f.WriteFloatString( "}" );
  744. SetText( f.GetDataPtr() );
  745. return true;
  746. }
  747. /*
  748. ================
  749. idDeclParticle::Save
  750. ================
  751. */
  752. bool idDeclParticle::Save( const char *fileName ) {
  753. RebuildTextSource();
  754. if ( fileName ) {
  755. declManager->CreateNewDecl( DECL_PARTICLE, GetName(), fileName );
  756. }
  757. ReplaceSourceFileText();
  758. return true;
  759. }
  760. /*
  761. ====================================================================================
  762. idParticleParm
  763. ====================================================================================
  764. */
  765. float idParticleParm::Eval( float frac, idRandom &rand ) const {
  766. if ( table ) {
  767. return table->TableLookup( frac );
  768. }
  769. return from + frac * ( to - from );
  770. }
  771. float idParticleParm::Integrate( float frac, idRandom &rand ) const {
  772. if ( table ) {
  773. common->Printf( "idParticleParm::Integrate: can't integrate tables\n" );
  774. return 0;
  775. }
  776. return ( from + frac * ( to - from ) * 0.5f ) * frac;
  777. }
  778. /*
  779. ====================================================================================
  780. idParticleStage
  781. ====================================================================================
  782. */
  783. /*
  784. ================
  785. idParticleStage::idParticleStage
  786. ================
  787. */
  788. idParticleStage::idParticleStage() {
  789. material = NULL;
  790. totalParticles = 0;
  791. cycles = 0.0f;
  792. cycleMsec = 0;
  793. spawnBunching = 0.0f;
  794. particleLife = 0.0f;
  795. timeOffset = 0.0f;
  796. deadTime = 0.0f;
  797. distributionType = PDIST_RECT;
  798. distributionParms[0] = distributionParms[1] = distributionParms[2] = distributionParms[3] = 0.0f;
  799. directionType = PDIR_CONE;
  800. directionParms[0] = directionParms[1] = directionParms[2] = directionParms[3] = 0.0f;
  801. // idParticleParm speed;
  802. gravity = 0.0f;
  803. worldGravity = false;
  804. customPathType = PPATH_STANDARD;
  805. customPathParms[0] = customPathParms[1] = customPathParms[2] = customPathParms[3] = 0.0f;
  806. customPathParms[4] = customPathParms[5] = customPathParms[6] = customPathParms[7] = 0.0f;
  807. offset.Zero();
  808. animationFrames = 0;
  809. animationRate = 0.0f;
  810. randomDistribution = true;
  811. entityColor = false;
  812. initialAngle = 0.0f;
  813. // idParticleParm rotationSpeed;
  814. orientation = POR_VIEW;
  815. orientationParms[0] = orientationParms[1] = orientationParms[2] = orientationParms[3] = 0.0f;
  816. // idParticleParm size
  817. // idParticleParm aspect
  818. color.Zero();
  819. fadeColor.Zero();
  820. fadeInFraction = 0.0f;
  821. fadeOutFraction = 0.0f;
  822. fadeIndexFraction = 0.0f;
  823. hidden = false;
  824. boundsExpansion = 0.0f;
  825. bounds.Clear();
  826. }
  827. /*
  828. ================
  829. idParticleStage::Default
  830. Sets the stage to a default state
  831. ================
  832. */
  833. void idParticleStage::Default() {
  834. material = declManager->FindMaterial( "_default" );
  835. totalParticles = 100;
  836. spawnBunching = 1.0f;
  837. particleLife = 1.5f;
  838. timeOffset = 0.0f;
  839. deadTime = 0.0f;
  840. distributionType = PDIST_RECT;
  841. distributionParms[0] = 8.0f;
  842. distributionParms[1] = 8.0f;
  843. distributionParms[2] = 8.0f;
  844. distributionParms[3] = 0.0f;
  845. directionType = PDIR_CONE;
  846. directionParms[0] = 90.0f;
  847. directionParms[1] = 0.0f;
  848. directionParms[2] = 0.0f;
  849. directionParms[3] = 0.0f;
  850. orientation = POR_VIEW;
  851. orientationParms[0] = 0.0f;
  852. orientationParms[1] = 0.0f;
  853. orientationParms[2] = 0.0f;
  854. orientationParms[3] = 0.0f;
  855. speed.from = 150.0f;
  856. speed.to = 150.0f;
  857. speed.table = NULL;
  858. gravity = 1.0f;
  859. worldGravity = false;
  860. customPathType = PPATH_STANDARD;
  861. customPathParms[0] = 0.0f;
  862. customPathParms[1] = 0.0f;
  863. customPathParms[2] = 0.0f;
  864. customPathParms[3] = 0.0f;
  865. customPathParms[4] = 0.0f;
  866. customPathParms[5] = 0.0f;
  867. customPathParms[6] = 0.0f;
  868. customPathParms[7] = 0.0f;
  869. offset.Zero();
  870. animationFrames = 0;
  871. animationRate = 0.0f;
  872. initialAngle = 0.0f;
  873. rotationSpeed.from = 0.0f;
  874. rotationSpeed.to = 0.0f;
  875. rotationSpeed.table = NULL;
  876. size.from = 4.0f;
  877. size.to = 4.0f;
  878. size.table = NULL;
  879. aspect.from = 1.0f;
  880. aspect.to = 1.0f;
  881. aspect.table = NULL;
  882. color.x = 1.0f;
  883. color.y = 1.0f;
  884. color.z = 1.0f;
  885. color.w = 1.0f;
  886. fadeColor.x = 0.0f;
  887. fadeColor.y = 0.0f;
  888. fadeColor.z = 0.0f;
  889. fadeColor.w = 0.0f;
  890. fadeInFraction = 0.1f;
  891. fadeOutFraction = 0.25f;
  892. fadeIndexFraction = 0.0f;
  893. boundsExpansion = 0.0f;
  894. randomDistribution = true;
  895. entityColor = false;
  896. cycleMsec = ( particleLife + deadTime ) * 1000;
  897. }
  898. /*
  899. ================
  900. idParticleStage::NumQuadsPerParticle
  901. includes trails and cross faded animations
  902. ================
  903. */
  904. int idParticleStage::NumQuadsPerParticle() const {
  905. int count = 1;
  906. if ( orientation == POR_AIMED ) {
  907. int trails = idMath::Ftoi( orientationParms[0] );
  908. // each trail stage will add an extra quad
  909. count *= ( 1 + trails );
  910. }
  911. // if we are doing strip-animation, we need to double the number and cross fade them
  912. if ( animationFrames > 1 ) {
  913. count *= 2;
  914. }
  915. return count;
  916. }
  917. /*
  918. ===============
  919. idParticleStage::ParticleOrigin
  920. ===============
  921. */
  922. void idParticleStage::ParticleOrigin( particleGen_t *g, idVec3 &origin ) const {
  923. if ( customPathType == PPATH_STANDARD ) {
  924. //
  925. // find intial origin distribution
  926. //
  927. float radiusSqr, angle1, angle2;
  928. switch( distributionType ) {
  929. case PDIST_RECT: { // ( sizeX sizeY sizeZ )
  930. origin[0] = ( ( randomDistribution ) ? g->random.CRandomFloat() : 1.0f ) * distributionParms[0];
  931. origin[1] = ( ( randomDistribution ) ? g->random.CRandomFloat() : 1.0f ) * distributionParms[1];
  932. origin[2] = ( ( randomDistribution ) ? g->random.CRandomFloat() : 1.0f ) * distributionParms[2];
  933. break;
  934. }
  935. case PDIST_CYLINDER: { // ( sizeX sizeY sizeZ ringFraction )
  936. angle1 = ( ( randomDistribution ) ? g->random.CRandomFloat() : 1.0f ) * idMath::TWO_PI;
  937. idMath::SinCos16( angle1, origin[0], origin[1] );
  938. origin[2] = ( ( randomDistribution ) ? g->random.CRandomFloat() : 1.0f );
  939. // reproject points that are inside the ringFraction to the outer band
  940. if ( distributionParms[3] > 0.0f ) {
  941. radiusSqr = origin[0] * origin[0] + origin[1] * origin[1];
  942. if ( radiusSqr < distributionParms[3] * distributionParms[3] ) {
  943. // if we are inside the inner reject zone, rescale to put it out into the good zone
  944. float f = sqrt( radiusSqr ) / distributionParms[3];
  945. float invf = 1.0f / f;
  946. float newRadius = distributionParms[3] + f * ( 1.0f - distributionParms[3] );
  947. float rescale = invf * newRadius;
  948. origin[0] *= rescale;
  949. origin[1] *= rescale;
  950. }
  951. }
  952. origin[0] *= distributionParms[0];
  953. origin[1] *= distributionParms[1];
  954. origin[2] *= distributionParms[2];
  955. break;
  956. }
  957. case PDIST_SPHERE: { // ( sizeX sizeY sizeZ ringFraction )
  958. // iterating with rejection is the only way to get an even distribution over a sphere
  959. if ( randomDistribution ) {
  960. do {
  961. origin[0] = g->random.CRandomFloat();
  962. origin[1] = g->random.CRandomFloat();
  963. origin[2] = g->random.CRandomFloat();
  964. radiusSqr = origin[0] * origin[0] + origin[1] * origin[1] + origin[2] * origin[2];
  965. } while( radiusSqr > 1.0f );
  966. } else {
  967. origin.Set( 1.0f, 1.0f, 1.0f );
  968. radiusSqr = 3.0f;
  969. }
  970. if ( distributionParms[3] > 0.0f ) {
  971. // we could iterate until we got something that also satisfied ringFraction,
  972. // but for narrow rings that could be a lot of work, so reproject inside points instead
  973. if ( radiusSqr < distributionParms[3] * distributionParms[3] ) {
  974. // if we are inside the inner reject zone, rescale to put it out into the good zone
  975. float f = sqrt( radiusSqr ) / distributionParms[3];
  976. float invf = 1.0f / f;
  977. float newRadius = distributionParms[3] + f * ( 1.0f - distributionParms[3] );
  978. float rescale = invf * newRadius;
  979. origin[0] *= rescale;
  980. origin[1] *= rescale;
  981. origin[2] *= rescale;
  982. }
  983. }
  984. origin[0] *= distributionParms[0];
  985. origin[1] *= distributionParms[1];
  986. origin[2] *= distributionParms[2];
  987. break;
  988. }
  989. }
  990. // offset will effect all particle origin types
  991. // add this before the velocity and gravity additions
  992. origin += offset;
  993. //
  994. // add the velocity over time
  995. //
  996. idVec3 dir;
  997. switch( directionType ) {
  998. case PDIR_CONE: {
  999. // angle is the full angle, so 360 degrees is any spherical direction
  1000. angle1 = g->random.CRandomFloat() * directionParms[0] * idMath::M_DEG2RAD;
  1001. angle2 = g->random.CRandomFloat() * idMath::PI;
  1002. float s1, c1, s2, c2;
  1003. idMath::SinCos16( angle1, s1, c1 );
  1004. idMath::SinCos16( angle2, s2, c2 );
  1005. dir[0] = s1 * c2;
  1006. dir[1] = s1 * s2;
  1007. dir[2] = c1;
  1008. break;
  1009. }
  1010. case PDIR_OUTWARD: {
  1011. dir = origin;
  1012. dir.Normalize();
  1013. dir[2] += directionParms[0];
  1014. break;
  1015. }
  1016. }
  1017. // add speed
  1018. float iSpeed = speed.Integrate( g->frac, g->random );
  1019. origin += dir * iSpeed * particleLife;
  1020. } else {
  1021. //
  1022. // custom paths completely override both the origin and velocity calculations, but still
  1023. // use the standard gravity
  1024. //
  1025. float angle1, angle2, speed1, speed2;
  1026. switch( customPathType ) {
  1027. case PPATH_HELIX: { // ( sizeX sizeY sizeZ radialSpeed axialSpeed )
  1028. speed1 = g->random.CRandomFloat();
  1029. speed2 = g->random.CRandomFloat();
  1030. angle1 = g->random.RandomFloat() * idMath::TWO_PI + customPathParms[3] * speed1 * g->age;
  1031. float s1, c1;
  1032. idMath::SinCos16( angle1, s1, c1 );
  1033. origin[0] = c1 * customPathParms[0];
  1034. origin[1] = s1 * customPathParms[1];
  1035. origin[2] = g->random.RandomFloat() * customPathParms[2] + customPathParms[4] * speed2 * g->age;
  1036. break;
  1037. }
  1038. case PPATH_FLIES: { // ( radialSpeed axialSpeed size )
  1039. speed1 = idMath::ClampFloat( 0.4f, 1.0f, g->random.CRandomFloat() );
  1040. speed2 = idMath::ClampFloat( 0.4f, 1.0f, g->random.CRandomFloat() );
  1041. angle1 = g->random.RandomFloat() * idMath::PI * 2 + customPathParms[0] * speed1 * g->age;
  1042. angle2 = g->random.RandomFloat() * idMath::PI * 2 + customPathParms[1] * speed1 * g->age;
  1043. float s1, c1, s2, c2;
  1044. idMath::SinCos16( angle1, s1, c1 );
  1045. idMath::SinCos16( angle2, s2, c2 );
  1046. origin[0] = c1 * c2;
  1047. origin[1] = s1 * c2;
  1048. origin[2] = -s2;
  1049. origin *= customPathParms[2];
  1050. break;
  1051. }
  1052. case PPATH_ORBIT: { // ( radius speed axis )
  1053. angle1 = g->random.RandomFloat() * idMath::TWO_PI + customPathParms[1] * g->age;
  1054. float s1, c1;
  1055. idMath::SinCos16( angle1, s1, c1 );
  1056. origin[0] = c1 * customPathParms[0];
  1057. origin[1] = s1 * customPathParms[0];
  1058. origin.ProjectSelfOntoSphere( customPathParms[0] );
  1059. break;
  1060. }
  1061. case PPATH_DRIP: { // ( speed )
  1062. origin[0] = 0.0f;
  1063. origin[1] = 0.0f;
  1064. origin[2] = -( g->age * customPathParms[0] );
  1065. break;
  1066. }
  1067. default: {
  1068. common->Error( "idParticleStage::ParticleOrigin: bad customPathType" );
  1069. }
  1070. }
  1071. origin += offset;
  1072. }
  1073. // adjust for the per-particle smoke offset
  1074. origin *= g->axis;
  1075. origin += g->origin;
  1076. // add gravity after adjusting for axis
  1077. if ( worldGravity ) {
  1078. idVec3 gra( 0, 0, -gravity );
  1079. gra *= g->renderEnt->axis.Transpose();
  1080. origin += gra * g->age * g->age;
  1081. } else {
  1082. origin[2] -= gravity * g->age * g->age;
  1083. }
  1084. }
  1085. /*
  1086. ==================
  1087. idParticleStage::ParticleVerts
  1088. ==================
  1089. */
  1090. int idParticleStage::ParticleVerts( particleGen_t *g, idVec3 origin, idDrawVert *verts ) const {
  1091. float psize = size.Eval( g->frac, g->random );
  1092. float paspect = aspect.Eval( g->frac, g->random );
  1093. float width = psize;
  1094. float height = psize * paspect;
  1095. idVec3 left, up;
  1096. if ( orientation == POR_AIMED ) {
  1097. // reset the values to an earlier time to get a previous origin
  1098. idRandom currentRandom = g->random;
  1099. float currentAge = g->age;
  1100. float currentFrac = g->frac;
  1101. idDrawVert *verts_p = verts;
  1102. idVec3 stepOrigin = origin;
  1103. idVec3 stepLeft;
  1104. int numTrails = idMath::Ftoi( orientationParms[0] );
  1105. float trailTime = orientationParms[1];
  1106. if ( trailTime == 0 ) {
  1107. trailTime = 0.5f;
  1108. }
  1109. float height = 1.0f / ( 1 + numTrails );
  1110. float t = 0;
  1111. for ( int i = 0 ; i <= numTrails ; i++ ) {
  1112. g->random = g->originalRandom;
  1113. g->age = currentAge - ( i + 1 ) * trailTime / ( numTrails + 1 ); // time to back up
  1114. g->frac = g->age / particleLife;
  1115. idVec3 oldOrigin;
  1116. ParticleOrigin( g, oldOrigin );
  1117. up = stepOrigin - oldOrigin; // along the direction of travel
  1118. idVec3 forwardDir;
  1119. g->renderEnt->axis.ProjectVector( g->renderView->viewaxis[0], forwardDir );
  1120. up -= ( up * forwardDir ) * forwardDir;
  1121. up.Normalize();
  1122. left = up.Cross( forwardDir );
  1123. left *= psize;
  1124. verts_p[0] = verts[0];
  1125. verts_p[1] = verts[1];
  1126. verts_p[2] = verts[2];
  1127. verts_p[3] = verts[3];
  1128. if ( i == 0 ) {
  1129. verts_p[0].xyz = stepOrigin - left;
  1130. verts_p[1].xyz = stepOrigin + left;
  1131. } else {
  1132. verts_p[0].xyz = stepOrigin - stepLeft;
  1133. verts_p[1].xyz = stepOrigin + stepLeft;
  1134. }
  1135. verts_p[2].xyz = oldOrigin - left;
  1136. verts_p[3].xyz = oldOrigin + left;
  1137. // modify texcoords
  1138. verts_p[0].SetTexCoordT( t );
  1139. verts_p[1].SetTexCoordT( t );
  1140. verts_p[2].SetTexCoordT( t + height );
  1141. verts_p[3].SetTexCoordT( t + height );
  1142. t += height;
  1143. verts_p += 4;
  1144. stepOrigin = oldOrigin;
  1145. stepLeft = left;
  1146. }
  1147. g->random = currentRandom;
  1148. g->age = currentAge;
  1149. g->frac = currentFrac;
  1150. return 4 * (numTrails+1);
  1151. }
  1152. //
  1153. // constant rotation
  1154. //
  1155. float angle;
  1156. angle = ( initialAngle ) ? initialAngle : 360 * g->random.RandomFloat();
  1157. float angleMove = rotationSpeed.Integrate( g->frac, g->random ) * particleLife;
  1158. // have hald the particles rotate each way
  1159. if ( g->index & 1 ) {
  1160. angle += angleMove;
  1161. } else {
  1162. angle -= angleMove;
  1163. }
  1164. angle = angle / 180 * idMath::PI;
  1165. float c = idMath::Cos16( angle );
  1166. float s = idMath::Sin16( angle );
  1167. if ( orientation == POR_Z ) {
  1168. // oriented in entity space
  1169. left[0] = s;
  1170. left[1] = c;
  1171. left[2] = 0;
  1172. up[0] = c;
  1173. up[1] = -s;
  1174. up[2] = 0;
  1175. } else if ( orientation == POR_X ) {
  1176. // oriented in entity space
  1177. left[0] = 0;
  1178. left[1] = c;
  1179. left[2] = s;
  1180. up[0] = 0;
  1181. up[1] = -s;
  1182. up[2] = c;
  1183. } else if ( orientation == POR_Y ) {
  1184. // oriented in entity space
  1185. left[0] = c;
  1186. left[1] = 0;
  1187. left[2] = s;
  1188. up[0] = -s;
  1189. up[1] = 0;
  1190. up[2] = c;
  1191. } else {
  1192. // oriented in viewer space
  1193. idVec3 entityLeft, entityUp;
  1194. g->renderEnt->axis.ProjectVector( g->renderView->viewaxis[1], entityLeft );
  1195. g->renderEnt->axis.ProjectVector( g->renderView->viewaxis[2], entityUp );
  1196. left = entityLeft * c + entityUp * s;
  1197. up = entityUp * c - entityLeft * s;
  1198. }
  1199. left *= width;
  1200. up *= height;
  1201. verts[0].xyz = origin - left + up;
  1202. verts[1].xyz = origin + left + up;
  1203. verts[2].xyz = origin - left - up;
  1204. verts[3].xyz = origin + left - up;
  1205. return 4;
  1206. }
  1207. /*
  1208. ==================
  1209. idParticleStage::ParticleTexCoords
  1210. ==================
  1211. */
  1212. void idParticleStage::ParticleTexCoords( particleGen_t *g, idDrawVert *verts ) const {
  1213. float s, width;
  1214. float t, height;
  1215. if ( animationFrames > 1 ) {
  1216. width = 1.0f / animationFrames;
  1217. float floatFrame;
  1218. if ( animationRate ) {
  1219. // explicit, cycling animation
  1220. floatFrame = g->age * animationRate;
  1221. } else {
  1222. // single animation cycle over the life of the particle
  1223. floatFrame = g->frac * animationFrames;
  1224. }
  1225. int intFrame = (int)floatFrame;
  1226. g->animationFrameFrac = floatFrame - intFrame;
  1227. s = width * intFrame;
  1228. } else {
  1229. s = 0.0f;
  1230. width = 1.0f;
  1231. }
  1232. t = 0.0f;
  1233. height = 1.0f;
  1234. verts[0].SetTexCoord( s, t );
  1235. verts[1].SetTexCoord( s+width, t );
  1236. verts[2].SetTexCoord( s, t+height );
  1237. verts[3].SetTexCoord( s+width, t+height );
  1238. }
  1239. /*
  1240. ==================
  1241. idParticleStage::ParticleColors
  1242. ==================
  1243. */
  1244. void idParticleStage::ParticleColors( particleGen_t *g, idDrawVert *verts ) const {
  1245. float fadeFraction = 1.0f;
  1246. // most particles fade in at the beginning and fade out at the end
  1247. if ( g->frac < fadeInFraction ) {
  1248. fadeFraction *= ( g->frac / fadeInFraction );
  1249. }
  1250. if ( 1.0f - g->frac < fadeOutFraction ) {
  1251. fadeFraction *= ( ( 1.0f - g->frac ) / fadeOutFraction );
  1252. }
  1253. // individual gun smoke particles get more and more faded as the
  1254. // cycle goes on (note that totalParticles won't be correct for a surface-particle deform)
  1255. if ( fadeIndexFraction ) {
  1256. float indexFrac = ( totalParticles - g->index ) / (float)totalParticles;
  1257. if ( indexFrac < fadeIndexFraction ) {
  1258. fadeFraction *= indexFrac / fadeIndexFraction;
  1259. }
  1260. }
  1261. for ( int i = 0 ; i < 4 ; i++ ) {
  1262. float fcolor = ( ( entityColor ) ? g->renderEnt->shaderParms[i] : color[i] ) * fadeFraction + fadeColor[i] * ( 1.0f - fadeFraction );
  1263. int icolor = idMath::Ftoi( fcolor * 255.0f );
  1264. if ( icolor < 0 ) {
  1265. icolor = 0;
  1266. } else if ( icolor > 255 ) {
  1267. icolor = 255;
  1268. }
  1269. verts[0].color[i] =
  1270. verts[1].color[i] =
  1271. verts[2].color[i] =
  1272. verts[3].color[i] = icolor;
  1273. }
  1274. }
  1275. /*
  1276. ================
  1277. idParticleStage::CreateParticle
  1278. Returns 0 if no particle is created because it is completely faded out
  1279. Returns 4 if a normal quad is created
  1280. Returns 8 if two cross faded quads are created
  1281. Vertex order is:
  1282. 0 1
  1283. 2 3
  1284. ================
  1285. */
  1286. int idParticleStage::CreateParticle( particleGen_t *g, idDrawVert *verts ) const {
  1287. idVec3 origin;
  1288. verts[0].Clear();
  1289. verts[1].Clear();
  1290. verts[2].Clear();
  1291. verts[3].Clear();
  1292. ParticleColors( g, verts );
  1293. // if we are completely faded out, kill the particle
  1294. if ( verts[0].color[0] == 0 && verts[0].color[1] == 0 && verts[0].color[2] == 0 && verts[0].color[3] == 0 ) {
  1295. return 0;
  1296. }
  1297. ParticleOrigin( g, origin );
  1298. ParticleTexCoords( g, verts );
  1299. int numVerts = ParticleVerts( g, origin, verts );
  1300. if ( animationFrames <= 1 ) {
  1301. return numVerts;
  1302. }
  1303. // if we are doing strip-animation, we need to double the quad and cross fade it
  1304. float width = 1.0f / animationFrames;
  1305. float frac = g->animationFrameFrac;
  1306. float iFrac = 1.0f - frac;
  1307. idVec2 tempST;
  1308. for ( int i = 0 ; i < numVerts ; i++ ) {
  1309. verts[numVerts + i] = verts[i];
  1310. tempST = verts[numVerts + i].GetTexCoord();
  1311. verts[numVerts + i].SetTexCoord( tempST.x + width, tempST.y );
  1312. verts[numVerts + i].color[0] *= frac;
  1313. verts[numVerts + i].color[1] *= frac;
  1314. verts[numVerts + i].color[2] *= frac;
  1315. verts[numVerts + i].color[3] *= frac;
  1316. verts[i].color[0] *= iFrac;
  1317. verts[i].color[1] *= iFrac;
  1318. verts[i].color[2] *= iFrac;
  1319. verts[i].color[3] *= iFrac;
  1320. }
  1321. return numVerts * 2;
  1322. }
  1323. /*
  1324. ==================
  1325. idParticleStage::GetCustomPathName
  1326. ==================
  1327. */
  1328. const char* idParticleStage::GetCustomPathName() {
  1329. int index = ( customPathType < CustomParticleCount ) ? customPathType : 0;
  1330. return ParticleCustomDesc[index].name;
  1331. }
  1332. /*
  1333. ==================
  1334. idParticleStage::GetCustomPathDesc
  1335. ==================
  1336. */
  1337. const char* idParticleStage::GetCustomPathDesc() {
  1338. int index = ( customPathType < CustomParticleCount ) ? customPathType : 0;
  1339. return ParticleCustomDesc[index].desc;
  1340. }
  1341. /*
  1342. ==================
  1343. idParticleStage::NumCustomPathParms
  1344. ==================
  1345. */
  1346. int idParticleStage::NumCustomPathParms() {
  1347. int index = ( customPathType < CustomParticleCount ) ? customPathType : 0;
  1348. return ParticleCustomDesc[index].count;
  1349. }
  1350. /*
  1351. ==================
  1352. idParticleStage::SetCustomPathType
  1353. ==================
  1354. */
  1355. void idParticleStage::SetCustomPathType( const char *p ) {
  1356. customPathType = PPATH_STANDARD;
  1357. for ( int i = 0; i < CustomParticleCount; i ++ ) {
  1358. if ( idStr::Icmp( p, ParticleCustomDesc[i].name ) == 0 ) {
  1359. customPathType = static_cast<prtCustomPth_t>( i );
  1360. break;
  1361. }
  1362. }
  1363. }
  1364. /*
  1365. ==================
  1366. idParticleStage::operator=
  1367. ==================
  1368. */
  1369. void idParticleStage::operator=( const idParticleStage &src ) {
  1370. material = src.material;
  1371. totalParticles = src.totalParticles;
  1372. cycles = src.cycles;
  1373. cycleMsec = src.cycleMsec;
  1374. spawnBunching = src.spawnBunching;
  1375. particleLife = src.particleLife;
  1376. timeOffset = src.timeOffset;
  1377. deadTime = src.deadTime;
  1378. distributionType = src.distributionType;
  1379. distributionParms[0] = src.distributionParms[0];
  1380. distributionParms[1] = src.distributionParms[1];
  1381. distributionParms[2] = src.distributionParms[2];
  1382. distributionParms[3] = src.distributionParms[3];
  1383. directionType = src.directionType;
  1384. directionParms[0] = src.directionParms[0];
  1385. directionParms[1] = src.directionParms[1];
  1386. directionParms[2] = src.directionParms[2];
  1387. directionParms[3] = src.directionParms[3];
  1388. speed = src.speed;
  1389. gravity = src.gravity;
  1390. worldGravity = src.worldGravity;
  1391. randomDistribution = src.randomDistribution;
  1392. entityColor = src.entityColor;
  1393. customPathType = src.customPathType;
  1394. customPathParms[0] = src.customPathParms[0];
  1395. customPathParms[1] = src.customPathParms[1];
  1396. customPathParms[2] = src.customPathParms[2];
  1397. customPathParms[3] = src.customPathParms[3];
  1398. customPathParms[4] = src.customPathParms[4];
  1399. customPathParms[5] = src.customPathParms[5];
  1400. customPathParms[6] = src.customPathParms[6];
  1401. customPathParms[7] = src.customPathParms[7];
  1402. offset = src.offset;
  1403. animationFrames = src.animationFrames;
  1404. animationRate = src.animationRate;
  1405. initialAngle = src.initialAngle;
  1406. rotationSpeed = src.rotationSpeed;
  1407. orientation = src.orientation;
  1408. orientationParms[0] = src.orientationParms[0];
  1409. orientationParms[1] = src.orientationParms[1];
  1410. orientationParms[2] = src.orientationParms[2];
  1411. orientationParms[3] = src.orientationParms[3];
  1412. size = src.size;
  1413. aspect = src.aspect;
  1414. color = src.color;
  1415. fadeColor = src.fadeColor;
  1416. fadeInFraction = src.fadeInFraction;
  1417. fadeOutFraction = src.fadeOutFraction;
  1418. fadeIndexFraction = src.fadeIndexFraction;
  1419. hidden = src.hidden;
  1420. boundsExpansion = src.boundsExpansion;
  1421. bounds = src.bounds;
  1422. }