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.

503 lines
13 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 "baseparticleentity.h"
  10. #include "particles_simple.h"
  11. #include "filesystem.h"
  12. // memdbgon must be the last include file in a .cpp file!!!
  13. #include "tier0/memdbgon.h"
  14. #ifdef HL2_EPISODIC
  15. #define SMOKESTACK_MAX_MATERIALS 8
  16. #else
  17. #define SMOKESTACK_MAX_MATERIALS 1
  18. #endif
  19. //==================================================
  20. // C_SmokeStack
  21. //==================================================
  22. class C_SmokeStack : public C_BaseParticleEntity, public IPrototypeAppEffect
  23. {
  24. public:
  25. DECLARE_CLIENTCLASS();
  26. DECLARE_CLASS( C_SmokeStack, C_BaseParticleEntity );
  27. C_SmokeStack();
  28. ~C_SmokeStack();
  29. class SmokeStackParticle : public Particle
  30. {
  31. public:
  32. Vector m_Velocity;
  33. Vector m_vAccel;
  34. float m_Lifetime;
  35. float m_flAngle;
  36. float m_flRollDelta;
  37. float m_flSortPos;
  38. };
  39. //C_BaseEntity
  40. public:
  41. virtual void OnDataChanged( DataUpdateType_t updateType );
  42. virtual void ClientThink();
  43. //IPrototypeAppEffect
  44. public:
  45. virtual void Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs);
  46. virtual bool GetPropEditInfo(RecvTable **ppTable, void **ppObj);
  47. //IParticleEffect
  48. public:
  49. virtual void Update(float fTimeDelta);
  50. virtual void RenderParticles( CParticleRenderIterator *pIterator );
  51. virtual void SimulateParticles( CParticleSimulateIterator *pIterator );
  52. virtual void StartRender( VMatrix &effectMatrix );
  53. private:
  54. void QueueLightParametersInRenderer();
  55. //Stuff from the datatable
  56. public:
  57. CParticleSphereRenderer m_Renderer;
  58. float m_SpreadSpeed;
  59. float m_Speed;
  60. float m_StartSize;
  61. float m_EndSize;
  62. float m_Rate;
  63. float m_JetLength; // Length of the jet. Lifetime is derived from this.
  64. int m_bEmit; // Emit particles?
  65. float m_flBaseSpread;
  66. class CLightInfo
  67. {
  68. public:
  69. Vector m_vPos;
  70. Vector m_vColor;
  71. float m_flIntensity;
  72. };
  73. // Note: there are two ways the directional light can be specified. The default is to use
  74. // DirLightColor and a default dirlight source (from above or below).
  75. // In this case, m_DirLight.m_vPos and m_DirLight.m_flIntensity are ignored.
  76. //
  77. // The other is to attach a directional env_particlelight to us.
  78. // In this case, m_DirLightSource is ignored and all the m_DirLight parameters are used.
  79. CParticleLightInfo m_AmbientLight;
  80. CParticleLightInfo m_DirLight;
  81. Vector m_vBaseColor;
  82. Vector m_vWind;
  83. float m_flTwist;
  84. int m_iMaterialModel;
  85. private:
  86. C_SmokeStack( const C_SmokeStack & );
  87. float m_TwistMat[2][2];
  88. int m_bTwist;
  89. float m_flAlphaScale;
  90. float m_InvLifetime; // Calculated from m_JetLength / m_Speed;
  91. CParticleMgr *m_pParticleMgr;
  92. PMaterialHandle m_MaterialHandle[SMOKESTACK_MAX_MATERIALS];
  93. TimedEvent m_ParticleSpawn;
  94. int m_iMaxFrames;
  95. bool m_bInView;
  96. float m_flRollSpeed;
  97. };
  98. // ------------------------------------------------------------------------- //
  99. // Tables.
  100. // ------------------------------------------------------------------------- //
  101. // Expose to the particle app.
  102. EXPOSE_PROTOTYPE_EFFECT(SmokeStack, C_SmokeStack);
  103. IMPLEMENT_CLIENTCLASS_DT(C_SmokeStack, DT_SmokeStack, CSmokeStack)
  104. RecvPropFloat(RECVINFO(m_SpreadSpeed), 0),
  105. RecvPropFloat(RECVINFO(m_Speed), 0),
  106. RecvPropFloat(RECVINFO(m_StartSize), 0),
  107. RecvPropFloat(RECVINFO(m_EndSize), 0),
  108. RecvPropFloat(RECVINFO(m_Rate), 0),
  109. RecvPropFloat(RECVINFO(m_JetLength), 0),
  110. RecvPropInt(RECVINFO(m_bEmit), 0),
  111. RecvPropFloat(RECVINFO(m_flBaseSpread)),
  112. RecvPropFloat(RECVINFO(m_flTwist)),
  113. RecvPropFloat(RECVINFO(m_flRollSpeed )),
  114. RecvPropIntWithMinusOneFlag( RECVINFO( m_iMaterialModel ) ),
  115. RecvPropVector( RECVINFO(m_AmbientLight.m_vPos) ),
  116. RecvPropVector( RECVINFO(m_AmbientLight.m_vColor) ),
  117. RecvPropFloat( RECVINFO(m_AmbientLight.m_flIntensity) ),
  118. RecvPropVector( RECVINFO(m_DirLight.m_vPos) ),
  119. RecvPropVector( RECVINFO(m_DirLight.m_vColor) ),
  120. RecvPropFloat( RECVINFO(m_DirLight.m_flIntensity) ),
  121. RecvPropVector(RECVINFO(m_vWind))
  122. END_RECV_TABLE()
  123. // ------------------------------------------------------------------------- //
  124. // C_SmokeStack implementation.
  125. // ------------------------------------------------------------------------- //
  126. C_SmokeStack::C_SmokeStack()
  127. {
  128. m_pParticleMgr = NULL;
  129. m_MaterialHandle[0] = INVALID_MATERIAL_HANDLE;
  130. m_iMaterialModel = -1;
  131. m_SpreadSpeed = 15;
  132. m_Speed = 30;
  133. m_StartSize = 10;
  134. m_EndSize = 15;
  135. m_Rate = 80;
  136. m_JetLength = 180;
  137. m_bEmit = true;
  138. m_flBaseSpread = 20;
  139. m_bInView = false;
  140. // Lighting is (base color) + (ambient / dist^2) + bump(directional / dist^2)
  141. // By default, we use bottom-up lighting for the directional.
  142. SetRenderColor( 0, 0, 0, 255 );
  143. m_AmbientLight.m_vPos.Init(0,0,-100);
  144. m_AmbientLight.m_vColor.Init( 40, 40, 40 );
  145. m_AmbientLight.m_flIntensity = 8000;
  146. m_DirLight.m_vColor.Init( 255, 128, 0 );
  147. m_vWind.Init();
  148. m_flTwist = 0;
  149. }
  150. C_SmokeStack::~C_SmokeStack()
  151. {
  152. if(m_pParticleMgr)
  153. m_pParticleMgr->RemoveEffect( &m_ParticleEffect );
  154. }
  155. //-----------------------------------------------------------------------------
  156. // Purpose: Called after a data update has occured
  157. // Input : bnewentity -
  158. //-----------------------------------------------------------------------------
  159. void C_SmokeStack::OnDataChanged(DataUpdateType_t updateType)
  160. {
  161. C_BaseEntity::OnDataChanged(updateType);
  162. if(updateType == DATA_UPDATE_CREATED)
  163. {
  164. Start(ParticleMgr(), NULL);
  165. }
  166. // Recalulate lifetime in case length or speed changed.
  167. m_InvLifetime = m_Speed / m_JetLength;
  168. }
  169. static ConVar mat_reduceparticles( "mat_reduceparticles", "0" );
  170. //-----------------------------------------------------------------------------
  171. // Purpose: Starts the effect
  172. // Input : *pParticleMgr -
  173. // *pArgs -
  174. //-----------------------------------------------------------------------------
  175. void C_SmokeStack::Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs)
  176. {
  177. pParticleMgr->AddEffect( &m_ParticleEffect, this );
  178. // Figure out the material name.
  179. char str[512] = "unset_material";
  180. const model_t *pModel = modelinfo->GetModel( m_iMaterialModel );
  181. if ( pModel )
  182. {
  183. Q_strncpy( str, modelinfo->GetModelName( pModel ), sizeof( str ) );
  184. // Get rid of the extension because the material system doesn't want it.
  185. char *pExt = Q_stristr( str, ".vmt" );
  186. if ( pExt )
  187. pExt[0] = 0;
  188. }
  189. m_MaterialHandle[0] = m_ParticleEffect.FindOrAddMaterial( str );
  190. #ifdef HL2_EPISODIC
  191. int iCount = 1;
  192. char szNames[512];
  193. int iLength = Q_strlen( str );
  194. str[iLength-1] = '\0';
  195. Q_snprintf( szNames, sizeof( szNames ), "%s%d.vmt", str, iCount );
  196. while ( filesystem->FileExists( VarArgs( "materials/%s", szNames ) ) && iCount < SMOKESTACK_MAX_MATERIALS )
  197. {
  198. char *pExt = Q_stristr( szNames, ".vmt" );
  199. if ( pExt )
  200. pExt[0] = 0;
  201. m_MaterialHandle[iCount] = m_ParticleEffect.FindOrAddMaterial( szNames );
  202. iCount++;
  203. }
  204. m_iMaxFrames = iCount-1;
  205. m_ParticleSpawn.Init( mat_reduceparticles.GetBool() ? m_Rate / 4 : m_Rate ); // Obey mat_reduceparticles in episodic
  206. #else
  207. m_ParticleSpawn.Init( m_Rate );
  208. #endif
  209. m_InvLifetime = m_Speed / m_JetLength;
  210. m_pParticleMgr = pParticleMgr;
  211. // Figure out how we need to draw.
  212. IMaterial *pMaterial = pParticleMgr->PMaterialToIMaterial( m_MaterialHandle[0] );
  213. if( pMaterial )
  214. {
  215. m_Renderer.Init( pParticleMgr, pMaterial );
  216. }
  217. QueueLightParametersInRenderer();
  218. // For the first N seconds, always simulate so it can build up the smokestack.
  219. // Afterwards, we set it to freeze when it's not being rendered.
  220. m_ParticleEffect.SetAlwaysSimulate( true );
  221. SetNextClientThink( gpGlobals->curtime + 5 );
  222. }
  223. void C_SmokeStack::ClientThink()
  224. {
  225. m_ParticleEffect.SetAlwaysSimulate( false );
  226. }
  227. //-----------------------------------------------------------------------------
  228. // Purpose:
  229. // Input : **ppTable -
  230. // **ppObj -
  231. // Output : Returns true on success, false on failure.
  232. //-----------------------------------------------------------------------------
  233. bool C_SmokeStack::GetPropEditInfo( RecvTable **ppTable, void **ppObj )
  234. {
  235. *ppTable = &REFERENCE_RECV_TABLE(DT_SmokeStack);
  236. *ppObj = this;
  237. return true;
  238. }
  239. //-----------------------------------------------------------------------------
  240. // Purpose:
  241. // Input : fTimeDelta -
  242. //-----------------------------------------------------------------------------
  243. void C_SmokeStack::Update(float fTimeDelta)
  244. {
  245. if( !m_pParticleMgr )
  246. {
  247. assert(false);
  248. return;
  249. }
  250. // Don't spawn particles unless we're visible.
  251. if( m_bEmit && (m_ParticleEffect.WasDrawnPrevFrame() || m_ParticleEffect.GetAlwaysSimulate()) )
  252. {
  253. // Add new particles.
  254. Vector forward, right, up;
  255. AngleVectors(GetAbsAngles(), &forward, &right, &up);
  256. float tempDelta = fTimeDelta;
  257. while(m_ParticleSpawn.NextEvent(tempDelta))
  258. {
  259. int iRandomFrame = random->RandomInt( 0, m_iMaxFrames );
  260. #ifndef HL2_EPISODIC
  261. iRandomFrame = 0;
  262. #endif
  263. // Make a new particle.
  264. if(SmokeStackParticle *pParticle = (SmokeStackParticle*)m_ParticleEffect.AddParticle(sizeof(SmokeStackParticle), m_MaterialHandle[iRandomFrame]))
  265. {
  266. float angle = FRand( 0, 2.0f*M_PI_F );
  267. pParticle->m_Pos = GetAbsOrigin() +
  268. right * (cos( angle ) * m_flBaseSpread) +
  269. forward * (sin( angle ) * m_flBaseSpread);
  270. pParticle->m_Velocity =
  271. FRand(-m_SpreadSpeed,m_SpreadSpeed) * right +
  272. FRand(-m_SpreadSpeed,m_SpreadSpeed) * forward +
  273. m_Speed * up;
  274. pParticle->m_vAccel = m_vWind;
  275. pParticle->m_Lifetime = 0;
  276. pParticle->m_flAngle = 0.0f;
  277. #ifdef HL2_EPISODIC
  278. pParticle->m_flAngle = RandomFloat( 0, 360 );
  279. #endif
  280. pParticle->m_flRollDelta = random->RandomFloat( -m_flRollSpeed, m_flRollSpeed );
  281. pParticle->m_flSortPos = pParticle->m_Pos.z;
  282. }
  283. }
  284. }
  285. // Setup the twist matrix.
  286. float flTwist = (m_flTwist * (M_PI_F * 2.f) / 360.0f) * Helper_GetFrameTime();
  287. if( ( m_bTwist = !!flTwist ) )
  288. {
  289. m_TwistMat[0][0] = cos(flTwist);
  290. m_TwistMat[0][1] = sin(flTwist);
  291. m_TwistMat[1][0] = -sin(flTwist);
  292. m_TwistMat[1][1] = cos(flTwist);
  293. }
  294. QueueLightParametersInRenderer();
  295. }
  296. void C_SmokeStack::StartRender( VMatrix &effectMatrix )
  297. {
  298. m_Renderer.StartRender( effectMatrix );
  299. }
  300. void C_SmokeStack::QueueLightParametersInRenderer()
  301. {
  302. m_Renderer.SetBaseColor( Vector( m_clrRender->r / 255.0f, m_clrRender->g / 255.0f, m_clrRender->b / 255.0f ) );
  303. m_Renderer.SetAmbientLight( m_AmbientLight );
  304. m_Renderer.SetDirectionalLight( m_DirLight );
  305. m_flAlphaScale = (float)m_clrRender->a;
  306. }
  307. void C_SmokeStack::RenderParticles( CParticleRenderIterator *pIterator )
  308. {
  309. const SmokeStackParticle *pParticle = (const SmokeStackParticle*)pIterator->GetFirst();
  310. while ( pParticle )
  311. {
  312. // Transform.
  313. Vector tPos;
  314. TransformParticle( m_pParticleMgr->GetModelView(), pParticle->m_Pos, tPos );
  315. // Figure out its alpha. Squaring it after it gets halfway through its lifetime
  316. // makes it get translucent and fade out for a longer time.
  317. //float alpha = cosf( -M_PI_F + tLifetime * M_PI_F * 2.f ) * 0.5f + 0.5f;
  318. float tLifetime = pParticle->m_Lifetime * m_InvLifetime;
  319. float alpha = TableCos( -M_PI_F + tLifetime * M_PI_F * 2.f ) * 0.5f + 0.5f;
  320. if( tLifetime > 0.5f )
  321. alpha *= alpha;
  322. m_Renderer.RenderParticle(
  323. pIterator->GetParticleDraw(),
  324. pParticle->m_Pos,
  325. tPos,
  326. alpha * m_flAlphaScale,
  327. FLerp(m_StartSize, m_EndSize, tLifetime),
  328. DEG2RAD( pParticle->m_flAngle )
  329. );
  330. pParticle = (const SmokeStackParticle*)pIterator->GetNext( pParticle->m_flSortPos );
  331. }
  332. }
  333. void C_SmokeStack::SimulateParticles( CParticleSimulateIterator *pIterator )
  334. {
  335. bool bSortNow = true; // Change this to false if we see sorting issues.
  336. bool bQuickTest = false;
  337. bool bDrawn = m_ParticleEffect.WasDrawnPrevFrame();
  338. if ( bDrawn == true && m_bInView == false )
  339. {
  340. bSortNow = true;
  341. }
  342. if ( bDrawn == false && m_bInView == true )
  343. {
  344. bQuickTest = true;
  345. }
  346. #ifndef HL2_EPISODIC
  347. bQuickTest = false;
  348. bSortNow = true;
  349. #endif
  350. if( bQuickTest == false && m_bEmit && (!m_ParticleEffect.WasDrawnPrevFrame() && !m_ParticleEffect.GetAlwaysSimulate()) )
  351. return;
  352. SmokeStackParticle *pParticle = (SmokeStackParticle*)pIterator->GetFirst();
  353. while ( pParticle )
  354. {
  355. // Should this particle die?
  356. pParticle->m_Lifetime += pIterator->GetTimeDelta();
  357. float tLifetime = pParticle->m_Lifetime * m_InvLifetime;
  358. if( tLifetime >= 1 )
  359. {
  360. pIterator->RemoveParticle( pParticle );
  361. }
  362. else
  363. {
  364. // Transform.
  365. if( m_bTwist )
  366. {
  367. Vector vTwist(
  368. pParticle->m_Pos.x - GetAbsOrigin().x,
  369. pParticle->m_Pos.y - GetAbsOrigin().y,
  370. 0);
  371. pParticle->m_Pos.x = vTwist.x * m_TwistMat[0][0] + vTwist.y * m_TwistMat[0][1] + GetAbsOrigin().x;
  372. pParticle->m_Pos.y = vTwist.x * m_TwistMat[1][0] + vTwist.y * m_TwistMat[1][1] + GetAbsOrigin().y;
  373. }
  374. #ifndef HL2_EPISODIC
  375. pParticle->m_Pos = pParticle->m_Pos +
  376. pParticle->m_Velocity * pIterator->GetTimeDelta() +
  377. pParticle->m_vAccel * (0.5f * pIterator->GetTimeDelta() * pIterator->GetTimeDelta());
  378. pParticle->m_Velocity += pParticle->m_vAccel * pIterator->GetTimeDelta();
  379. #else
  380. pParticle->m_Pos = pParticle->m_Pos + pParticle->m_Velocity * pIterator->GetTimeDelta() + pParticle->m_vAccel * pIterator->GetTimeDelta();
  381. #endif
  382. pParticle->m_flAngle += pParticle->m_flRollDelta * pIterator->GetTimeDelta();
  383. if ( bSortNow == true )
  384. {
  385. Vector tPos;
  386. TransformParticle( m_pParticleMgr->GetModelView(), pParticle->m_Pos, tPos );
  387. pParticle->m_flSortPos = tPos.z;
  388. }
  389. }
  390. pParticle = (SmokeStackParticle*)pIterator->GetNext();
  391. }
  392. m_bInView = bDrawn;
  393. }