Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

525 lines
14 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Implements a particle system steam jet.
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "particle_prototype.h"
  9. #include "particle_util.h"
  10. #include "baseparticleentity.h"
  11. #include "clienteffectprecachesystem.h"
  12. #include "fx.h"
  13. // memdbgon must be the last include file in a .cpp file!!!
  14. #include "tier0/memdbgon.h"
  15. //NOTENOTE: Mirrored in dlls\steamjet.h
  16. #define STEAM_NORMAL 0
  17. #define STEAM_HEATWAVE 1
  18. #define STEAMJET_NUMRAMPS 5
  19. #define SF_EMISSIVE 0x00000001
  20. //==================================================
  21. // C_SteamJet
  22. //==================================================
  23. class C_SteamJet : public C_BaseParticleEntity, public IPrototypeAppEffect
  24. {
  25. public:
  26. DECLARE_CLIENTCLASS();
  27. DECLARE_CLASS( C_SteamJet, C_BaseParticleEntity );
  28. C_SteamJet();
  29. ~C_SteamJet();
  30. class SteamJetParticle : public Particle
  31. {
  32. public:
  33. Vector m_Velocity;
  34. float m_flRoll;
  35. float m_flRollDelta;
  36. float m_Lifetime;
  37. float m_DieTime;
  38. unsigned char m_uchStartSize;
  39. unsigned char m_uchEndSize;
  40. };
  41. int IsEmissive( void ) { return ( m_spawnflags & SF_EMISSIVE ); }
  42. //C_BaseEntity
  43. public:
  44. virtual void OnDataChanged( DataUpdateType_t updateType );
  45. //IPrototypeAppEffect
  46. public:
  47. virtual void Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs);
  48. virtual bool GetPropEditInfo(RecvTable **ppTable, void **ppObj);
  49. //IParticleEffect
  50. public:
  51. virtual void Update(float fTimeDelta);
  52. virtual void RenderParticles( CParticleRenderIterator *pIterator );
  53. virtual void SimulateParticles( CParticleSimulateIterator *pIterator );
  54. //Stuff from the datatable
  55. public:
  56. float m_SpreadSpeed;
  57. float m_Speed;
  58. float m_StartSize;
  59. float m_EndSize;
  60. float m_Rate;
  61. float m_JetLength; // Length of the jet. Lifetime is derived from this.
  62. int m_bEmit; // Emit particles?
  63. int m_nType; // Type of particles to emit
  64. bool m_bFaceLeft; // For support of legacy env_steamjet entity, which faced left instead of forward.
  65. int m_spawnflags;
  66. float m_flRollSpeed;
  67. private:
  68. void UpdateLightingRamp();
  69. private:
  70. // Stored the last time it updates the lighting ramp, so it can cache the values.
  71. Vector m_vLastRampUpdatePos;
  72. QAngle m_vLastRampUpdateAngles;
  73. float m_Lifetime; // Calculated from m_JetLength / m_Speed;
  74. // We sample the world to get these colors and ramp the particles.
  75. Vector m_Ramps[STEAMJET_NUMRAMPS];
  76. CParticleMgr *m_pParticleMgr;
  77. PMaterialHandle m_MaterialHandle;
  78. TimedEvent m_ParticleSpawn;
  79. private:
  80. C_SteamJet( const C_SteamJet & );
  81. };
  82. // ------------------------------------------------------------------------- //
  83. // Tables.
  84. // ------------------------------------------------------------------------- //
  85. // Expose to the particle app.
  86. EXPOSE_PROTOTYPE_EFFECT(SteamJet, C_SteamJet);
  87. // Datatable..
  88. IMPLEMENT_CLIENTCLASS_DT(C_SteamJet, DT_SteamJet, CSteamJet)
  89. RecvPropFloat(RECVINFO(m_SpreadSpeed), 0),
  90. RecvPropFloat(RECVINFO(m_Speed), 0),
  91. RecvPropFloat(RECVINFO(m_StartSize), 0),
  92. RecvPropFloat(RECVINFO(m_EndSize), 0),
  93. RecvPropFloat(RECVINFO(m_Rate), 0),
  94. RecvPropFloat(RECVINFO(m_JetLength), 0),
  95. RecvPropInt(RECVINFO(m_bEmit), 0),
  96. RecvPropInt(RECVINFO(m_bFaceLeft), 0),
  97. RecvPropInt(RECVINFO(m_nType), 0),
  98. RecvPropInt( RECVINFO( m_spawnflags ) ),
  99. RecvPropFloat(RECVINFO(m_flRollSpeed), 0 ),
  100. END_RECV_TABLE()
  101. // ------------------------------------------------------------------------- //
  102. // C_SteamJet implementation.
  103. // ------------------------------------------------------------------------- //
  104. C_SteamJet::C_SteamJet()
  105. {
  106. m_pParticleMgr = NULL;
  107. m_MaterialHandle = INVALID_MATERIAL_HANDLE;
  108. m_SpreadSpeed = 15;
  109. m_Speed = 120;
  110. m_StartSize = 10;
  111. m_EndSize = 25;
  112. m_Rate = 26;
  113. m_JetLength = 80;
  114. m_bEmit = true;
  115. m_bFaceLeft = false;
  116. m_ParticleEffect.SetAlwaysSimulate( false ); // Don't simulate outside the PVS or frustum.
  117. m_vLastRampUpdatePos.Init( 1e24, 1e24, 1e24 );
  118. m_vLastRampUpdateAngles.Init( 1e24, 1e24, 1e24 );
  119. }
  120. C_SteamJet::~C_SteamJet()
  121. {
  122. if(m_pParticleMgr)
  123. m_pParticleMgr->RemoveEffect( &m_ParticleEffect );
  124. }
  125. //-----------------------------------------------------------------------------
  126. // Purpose: Called after a data update has occured
  127. // Input : bnewentity -
  128. //-----------------------------------------------------------------------------
  129. void C_SteamJet::OnDataChanged(DataUpdateType_t updateType)
  130. {
  131. C_BaseEntity::OnDataChanged(updateType);
  132. if(updateType == DATA_UPDATE_CREATED)
  133. {
  134. Start(ParticleMgr(), NULL);
  135. }
  136. // Recalulate lifetime in case length or speed changed.
  137. m_Lifetime = m_JetLength / m_Speed;
  138. m_ParticleEffect.SetParticleCullRadius( MAX(m_StartSize, m_EndSize) );
  139. }
  140. //-----------------------------------------------------------------------------
  141. // Purpose: Starts the effect
  142. // Input : *pParticleMgr -
  143. // *pArgs -
  144. //-----------------------------------------------------------------------------
  145. void C_SteamJet::Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs)
  146. {
  147. pParticleMgr->AddEffect( &m_ParticleEffect, this );
  148. switch(m_nType)
  149. {
  150. case STEAM_NORMAL:
  151. default:
  152. m_MaterialHandle = g_Mat_DustPuff[0];
  153. break;
  154. case STEAM_HEATWAVE:
  155. m_MaterialHandle = m_ParticleEffect.FindOrAddMaterial("sprites/heatwave");
  156. break;
  157. }
  158. m_ParticleSpawn.Init(m_Rate);
  159. m_Lifetime = m_JetLength / m_Speed;
  160. m_pParticleMgr = pParticleMgr;
  161. UpdateLightingRamp();
  162. }
  163. //-----------------------------------------------------------------------------
  164. // Purpose:
  165. // Input : **ppTable -
  166. // **ppObj -
  167. // Output : Returns true on success, false on failure.
  168. //-----------------------------------------------------------------------------
  169. bool C_SteamJet::GetPropEditInfo( RecvTable **ppTable, void **ppObj )
  170. {
  171. *ppTable = &REFERENCE_RECV_TABLE(DT_SteamJet);
  172. *ppObj = this;
  173. return true;
  174. }
  175. // This might be useful someday.
  176. /*
  177. void CalcFastApproximateRenderBoundsAABB( C_BaseEntity *pEnt, float flBloatSize, Vector *pMin, Vector *pMax )
  178. {
  179. C_BaseEntity *pParent = pEnt->GetMoveParent();
  180. if ( pParent )
  181. {
  182. // Get the parent's abs space world bounds.
  183. CalcFastApproximateRenderBoundsAABB( pParent, 0, pMin, pMax );
  184. // Add the maximum of our local render bounds. This is making the assumption that we can be at any
  185. // point and at any angle within the parent's world space bounds.
  186. Vector vAddMins, vAddMaxs;
  187. pEnt->GetRenderBounds( vAddMins, vAddMaxs );
  188. flBloatSize += MAX( vAddMins.Length(), vAddMaxs.Length() );
  189. }
  190. else
  191. {
  192. // Start out with our own render bounds. Since we don't have a parent, this won't incur any nasty
  193. pEnt->GetRenderBoundsWorldspace( *pMin, *pMax );
  194. }
  195. // Bloat the box.
  196. if ( flBloatSize )
  197. {
  198. *pMin -= Vector( flBloatSize, flBloatSize, flBloatSize );
  199. *pMax += Vector( flBloatSize, flBloatSize, flBloatSize );
  200. }
  201. }
  202. */
  203. //-----------------------------------------------------------------------------
  204. // Purpose:
  205. // Input : fTimeDelta -
  206. //-----------------------------------------------------------------------------
  207. void C_SteamJet::Update(float fTimeDelta)
  208. {
  209. if(!m_pParticleMgr)
  210. {
  211. assert(false);
  212. return;
  213. }
  214. if( m_bEmit )
  215. {
  216. // Add new particles.
  217. int nToEmit = 0;
  218. float tempDelta = fTimeDelta;
  219. while( m_ParticleSpawn.NextEvent(tempDelta) )
  220. ++nToEmit;
  221. if ( nToEmit > 0 )
  222. {
  223. Vector forward, right, up;
  224. AngleVectors(GetAbsAngles(), &forward, &right, &up);
  225. // Legacy env_steamjet entities faced left instead of forward.
  226. if (m_bFaceLeft)
  227. {
  228. Vector temp = forward;
  229. forward = -right;
  230. right = temp;
  231. }
  232. // EVIL: Ideally, we could tell the renderer our OBB, and let it build a big box that encloses
  233. // the entity with its parent so it doesn't have to setup its parent's bones here.
  234. Vector vEndPoint = GetAbsOrigin() + forward * m_Speed;
  235. Vector vMin, vMax;
  236. VectorMin( GetAbsOrigin(), vEndPoint, vMin );
  237. VectorMax( GetAbsOrigin(), vEndPoint, vMax );
  238. m_ParticleEffect.SetBBox( vMin, vMax );
  239. if ( m_ParticleEffect.WasDrawnPrevFrame() )
  240. {
  241. while ( nToEmit-- )
  242. {
  243. // Make a new particle.
  244. if( SteamJetParticle *pParticle = (SteamJetParticle*) m_ParticleEffect.AddParticle( sizeof(SteamJetParticle), m_MaterialHandle ) )
  245. {
  246. pParticle->m_Pos = GetAbsOrigin();
  247. pParticle->m_Velocity =
  248. FRand(-m_SpreadSpeed,m_SpreadSpeed) * right +
  249. FRand(-m_SpreadSpeed,m_SpreadSpeed) * up +
  250. m_Speed * forward;
  251. pParticle->m_Lifetime = 0;
  252. pParticle->m_DieTime = m_Lifetime;
  253. pParticle->m_uchStartSize = m_StartSize;
  254. pParticle->m_uchEndSize = m_EndSize;
  255. pParticle->m_flRoll = random->RandomFloat( 0, 360 );
  256. pParticle->m_flRollDelta = random->RandomFloat( -m_flRollSpeed, m_flRollSpeed );
  257. }
  258. }
  259. }
  260. UpdateLightingRamp();
  261. }
  262. }
  263. }
  264. // Render a quad on the screen where you pass in color and size.
  265. // Normal is random and "flutters"
  266. inline void RenderParticle_ColorSizePerturbNormal(
  267. ParticleDraw* pDraw,
  268. const Vector &pos,
  269. const Vector &color,
  270. const float alpha,
  271. const float size
  272. )
  273. {
  274. // Don't render totally transparent particles.
  275. if( alpha < 0.001f )
  276. return;
  277. CMeshBuilder *pBuilder = pDraw->GetMeshBuilder();
  278. if( !pBuilder )
  279. return;
  280. unsigned char ubColor[4];
  281. ubColor[0] = (unsigned char)RoundFloatToInt( color.x * 254.9f );
  282. ubColor[1] = (unsigned char)RoundFloatToInt( color.y * 254.9f );
  283. ubColor[2] = (unsigned char)RoundFloatToInt( color.z * 254.9f );
  284. ubColor[3] = (unsigned char)RoundFloatToInt( alpha * 254.9f );
  285. Vector vNorm;
  286. vNorm.Random( -1.0f, 1.0f );
  287. // Add the 4 corner vertices.
  288. pBuilder->Position3f( pos.x-size, pos.y-size, pos.z );
  289. pBuilder->Color4ubv( ubColor );
  290. pBuilder->Normal3fv( vNorm.Base() );
  291. pBuilder->TexCoord2f( 0, 0, 1.0f );
  292. pBuilder->AdvanceVertex();
  293. pBuilder->Position3f( pos.x-size, pos.y+size, pos.z );
  294. pBuilder->Color4ubv( ubColor );
  295. pBuilder->Normal3fv( vNorm.Base() );
  296. pBuilder->TexCoord2f( 0, 0, 0 );
  297. pBuilder->AdvanceVertex();
  298. pBuilder->Position3f( pos.x+size, pos.y+size, pos.z );
  299. pBuilder->Color4ubv( ubColor );
  300. pBuilder->Normal3fv( vNorm.Base() );
  301. pBuilder->TexCoord2f( 0, 1.0f, 0 );
  302. pBuilder->AdvanceVertex();
  303. pBuilder->Position3f( pos.x+size, pos.y-size, pos.z );
  304. pBuilder->Color4ubv( ubColor );
  305. pBuilder->Normal3fv( vNorm.Base() );
  306. pBuilder->TexCoord2f( 0, 1.0f, 1.0f );
  307. pBuilder->AdvanceVertex();
  308. }
  309. void C_SteamJet::RenderParticles( CParticleRenderIterator *pIterator )
  310. {
  311. const SteamJetParticle *pParticle = (const SteamJetParticle*)pIterator->GetFirst();
  312. while ( pParticle )
  313. {
  314. // Render.
  315. Vector tPos;
  316. TransformParticle(m_pParticleMgr->GetModelView(), pParticle->m_Pos, tPos);
  317. float sortKey = tPos.z;
  318. float lifetimeT = pParticle->m_Lifetime / (pParticle->m_DieTime + 0.001);
  319. float fRamp = lifetimeT * (STEAMJET_NUMRAMPS-1);
  320. int iRamp = (int)fRamp;
  321. float fraction = fRamp - iRamp;
  322. Vector vRampColor = m_Ramps[iRamp] + (m_Ramps[iRamp+1] - m_Ramps[iRamp]) * fraction;
  323. vRampColor[0] = MIN( 1.0f, vRampColor[0] );
  324. vRampColor[1] = MIN( 1.0f, vRampColor[1] );
  325. vRampColor[2] = MIN( 1.0f, vRampColor[2] );
  326. float sinLifetime = sin(pParticle->m_Lifetime * 3.14159f / pParticle->m_DieTime);
  327. if ( m_nType == STEAM_HEATWAVE )
  328. {
  329. RenderParticle_ColorSizePerturbNormal(
  330. pIterator->GetParticleDraw(),
  331. tPos,
  332. vRampColor,
  333. sinLifetime * (m_clrRender->a/255.0f),
  334. FLerp(m_StartSize, m_EndSize, pParticle->m_Lifetime));
  335. }
  336. else
  337. {
  338. RenderParticle_ColorSizeAngle(
  339. pIterator->GetParticleDraw(),
  340. tPos,
  341. vRampColor,
  342. sinLifetime * (m_clrRender->a/255.0f),
  343. FLerp(pParticle->m_uchStartSize, pParticle->m_uchEndSize, pParticle->m_Lifetime),
  344. pParticle->m_flRoll );
  345. }
  346. pParticle = (const SteamJetParticle*)pIterator->GetNext( sortKey );
  347. }
  348. }
  349. void C_SteamJet::SimulateParticles( CParticleSimulateIterator *pIterator )
  350. {
  351. //Don't simulate if we're emiting particles...
  352. //This fixes the cases where looking away from a steam jet and then looking back would cause a break on the stream.
  353. if ( m_ParticleEffect.WasDrawnPrevFrame() == false && m_bEmit )
  354. return;
  355. SteamJetParticle *pParticle = (SteamJetParticle*)pIterator->GetFirst();
  356. while ( pParticle )
  357. {
  358. // Should this particle die?
  359. pParticle->m_Lifetime += pIterator->GetTimeDelta();
  360. if( pParticle->m_Lifetime > pParticle->m_DieTime )
  361. {
  362. pIterator->RemoveParticle( pParticle );
  363. }
  364. else
  365. {
  366. pParticle->m_flRoll += pParticle->m_flRollDelta * pIterator->GetTimeDelta();
  367. pParticle->m_Pos = pParticle->m_Pos + pParticle->m_Velocity * pIterator->GetTimeDelta();
  368. }
  369. pParticle = (SteamJetParticle*)pIterator->GetNext();
  370. }
  371. }
  372. void C_SteamJet::UpdateLightingRamp()
  373. {
  374. if( VectorsAreEqual( m_vLastRampUpdatePos, GetAbsOrigin(), 0.1 ) &&
  375. QAnglesAreEqual( m_vLastRampUpdateAngles, GetAbsAngles(), 0.1 ) )
  376. {
  377. return;
  378. }
  379. m_vLastRampUpdatePos = GetAbsOrigin();
  380. m_vLastRampUpdateAngles = GetAbsAngles();
  381. // Sample the world lighting where we think the particles will be.
  382. Vector forward, right, up;
  383. AngleVectors(GetAbsAngles(), &forward, &right, &up);
  384. // Legacy env_steamjet entities faced left instead of forward.
  385. if (m_bFaceLeft)
  386. {
  387. Vector temp = forward;
  388. forward = -right;
  389. right = temp;
  390. }
  391. Vector startPos = GetAbsOrigin();
  392. Vector endPos = GetAbsOrigin() + forward * (m_Speed * m_Lifetime);
  393. for(int iRamp=0; iRamp < STEAMJET_NUMRAMPS; iRamp++)
  394. {
  395. float t = (float)iRamp / (STEAMJET_NUMRAMPS-1);
  396. Vector vTestPos = startPos + (endPos - startPos) * t;
  397. Vector *pRamp = &m_Ramps[iRamp];
  398. *pRamp = WorldGetLightForPoint(vTestPos, false);
  399. if ( IsEmissive() )
  400. {
  401. pRamp->x += (m_clrRender->r/255.0f);
  402. pRamp->y += (m_clrRender->g/255.0f);
  403. pRamp->z += (m_clrRender->b/255.0f);
  404. pRamp->x = clamp( pRamp->x, 0.0f, 1.0f );
  405. pRamp->y = clamp( pRamp->y, 0.0f, 1.0f );
  406. pRamp->z = clamp( pRamp->z, 0.0f, 1.0f );
  407. }
  408. else
  409. {
  410. pRamp->x *= (m_clrRender->r/255.0f);
  411. pRamp->y *= (m_clrRender->g/255.0f);
  412. pRamp->z *= (m_clrRender->b/255.0f);
  413. }
  414. // Renormalize?
  415. float maxVal = MAX(pRamp->x, MAX(pRamp->y, pRamp->z));
  416. if(maxVal > 1)
  417. {
  418. *pRamp = *pRamp / maxVal;
  419. }
  420. }
  421. }