void CLocalSpaceEmitter::RenderParticles( CParticleRenderIterator *pIterator )
{
	const matrix3x4_t &mLocalToWorld = GetTransformMatrix();
	const VMatrix &mModelView = ParticleMgr()->GetModelView();

	const SimpleParticle *pParticle = (const SimpleParticle *)pIterator->GetFirst();
	while ( pParticle )
	{
		// Transform it
		Vector screenPos, worldPos;
		VectorTransform( pParticle->m_Pos, mLocalToWorld, worldPos );
		
		// Correct viewmodel squashing
		if ( m_fFlags & FLE_VIEWMODEL )
		{
			FormatViewModelAttachment( NULL, worldPos, false );
		}

		TransformParticle( mModelView, worldPos, screenPos );
		
		float sortKey = (int) screenPos.z;

		// Render it
		RenderParticle_ColorSizeAngle(
			pIterator->GetParticleDraw(),
			screenPos,
			UpdateColor( pParticle ),
			UpdateAlpha( pParticle ) * GetAlphaDistanceFade( screenPos, m_flNearClipMin, m_flNearClipMax ),
			UpdateScale( pParticle ),
			pParticle->m_flRoll 
			);

		pParticle = (const SimpleParticle *)pIterator->GetNext( sortKey );
	}
}
//-----------------------------------------------------------------------------
// Purpose: Simulate motion and render all child particles
// Input  : *pInParticle - 
//			*pDraw - 
//			&sortKey - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CSimpleEmitter::SimulateAndRender( Particle *pInParticle, ParticleDraw *pDraw, float &sortKey)
{
	SimpleParticle *pParticle = (SimpleParticle *) pInParticle;
	float timeDelta = pDraw->GetTimeDelta();

	//Render
	Vector	tPos;

	TransformParticle( g_ParticleMgr.GetModelView(), pParticle->m_Pos, tPos );
	sortKey = (int) tPos.z;

	//Render it
	RenderParticle_ColorSizeAngle(
		pDraw,
		tPos,
		UpdateColor( pParticle, timeDelta ),
		UpdateAlpha( pParticle, timeDelta ) * GetAlphaDistanceFade( tPos, m_flNearClipMin, m_flNearClipMax ),
		UpdateScale( pParticle, timeDelta ),
		UpdateRoll( pParticle, timeDelta ) );

	//Update velocity
	UpdateVelocity( pParticle, timeDelta );
	pParticle->m_Pos += pParticle->m_vecVelocity * timeDelta;

	//Should this particle die?
	pParticle->m_flLifetime += timeDelta;

	if ( pParticle->m_flLifetime >= pParticle->m_flDieTime )
		return false;

	return true;
}
void C_SteamJet::RenderParticles( CParticleRenderIterator *pIterator )
{
	const SteamJetParticle *pParticle = (const SteamJetParticle*)pIterator->GetFirst();
	while ( pParticle )
	{
		// Render.
		Vector tPos;
		TransformParticle(m_pParticleMgr->GetModelView(), pParticle->m_Pos, tPos);
		float sortKey = tPos.z;

		float lifetimeT = pParticle->m_Lifetime / (pParticle->m_DieTime + 0.001);
		float fRamp = lifetimeT * (STEAMJET_NUMRAMPS-1);
		int iRamp = (int)fRamp;
		float fraction = fRamp - iRamp;
		
		Vector vRampColor = m_Ramps[iRamp] + (m_Ramps[iRamp+1] - m_Ramps[iRamp]) * fraction;

		vRampColor[0] = min( 1.0f, vRampColor[0] );
		vRampColor[1] = min( 1.0f, vRampColor[1] );
		vRampColor[2] = min( 1.0f, vRampColor[2] );

#ifdef GE_DLL
		// Determine a linear alpha falloff based on our limits
		float alphamod = 1.0f;
		float fadetime = gpGlobals->curtime - m_flStartFadeTime;
		if ( m_bIsForExplosion && fadetime > 0 && m_flStartFadeTime > 0 )
			alphamod = RemapValClamped( fadetime, 0, m_flFadeDuration, 1.0f, 0 );

		float sinLifetime = sin(pParticle->m_Lifetime * 3.14159f / pParticle->m_DieTime) * alphamod;
#else
		float sinLifetime = sin(pParticle->m_Lifetime * 3.14159f / pParticle->m_DieTime);
#endif

		if ( m_nType == STEAM_HEATWAVE )
		{
			RenderParticle_ColorSizePerturbNormal(
				pIterator->GetParticleDraw(),
				tPos,
				vRampColor,
				sinLifetime * (m_clrRender->a/255.0f),
				FLerp(m_StartSize, m_EndSize, pParticle->m_Lifetime));
		}
		else
		{
			RenderParticle_ColorSizeAngle(
				pIterator->GetParticleDraw(),
				tPos,
				vRampColor,
				sinLifetime * (m_clrRender->a/255.0f),
				FLerp(pParticle->m_uchStartSize, pParticle->m_uchEndSize, pParticle->m_Lifetime),
				pParticle->m_flRoll );
		}

		pParticle = (const SteamJetParticle*)pIterator->GetNext( sortKey );
	}
}
void C_FuncSmokeVolume::RenderParticles( CParticleRenderIterator *pIterator )
{
	if ( m_CurrentDensity == 0 )
		return;

	const SmokeGrenadeParticle *pParticle = (const SmokeGrenadeParticle*)pIterator->GetFirst();
	while ( pParticle )
	{
		Vector renderPos = pParticle->m_Pos;

		// Fade out globally.
		float alpha = m_CurrentDensity;

		// Apply the precalculated fade alpha from world geometry.
		alpha *= pParticle->m_FadeAlpha;
		
		// TODO: optimize this whole routine!
		Vector color = m_MinColor + (m_MaxColor - m_MinColor) * (pParticle->m_ColorInterp / 255.1f);
		if ( IsEmissive() )
		{
			color.x += pParticle->m_Color[0] / 255.0f;
			color.y += pParticle->m_Color[1] / 255.0f;
			color.z += pParticle->m_Color[2] / 255.0f;

			color.x = clamp( color.x, 0.0f, 1.0f );
			color.y = clamp( color.y, 0.0f, 1.0f );
			color.z = clamp( color.z, 0.0f, 1.0f );
		}
		else
		{
			color.x *= pParticle->m_Color[0] / 255.0f;
			color.y *= pParticle->m_Color[1] / 255.0f;
			color.z *= pParticle->m_Color[2] / 255.0f;
		}
		
		Vector tRenderPos;
		TransformParticle( ParticleMgr()->GetModelView(), renderPos, tRenderPos );
		float sortKey = 1;//tRenderPos.z;

		RenderParticle_ColorSizeAngle(
			pIterator->GetParticleDraw(),
			tRenderPos,
			color,
			alpha * GetAlphaDistanceFade(tRenderPos, 10, 30),	// Alpha
			m_ParticleRadius,
			pParticle->m_CurRotation
			);

		pParticle = (const SmokeGrenadeParticle*)pIterator->GetNext( sortKey );
	}
}
Example #5
0
void C_SteamJet::RenderParticles( CParticleRenderIterator *pIterator )
{
	const SteamJetParticle *pParticle = (const SteamJetParticle*)pIterator->GetFirst();
	while ( pParticle )
	{
		// Render.
		Vector tPos;
		TransformParticle(m_pParticleMgr->GetModelView(), pParticle->m_Pos, tPos);
		float sortKey = tPos.z;

		float lifetimeT = pParticle->m_Lifetime / (pParticle->m_DieTime + 0.001);
		float fRamp = lifetimeT * (STEAMJET_NUMRAMPS-1);
		int iRamp = (int)fRamp;
		float fraction = fRamp - iRamp;
		
		Vector vRampColor = m_Ramps[iRamp] + (m_Ramps[iRamp+1] - m_Ramps[iRamp]) * fraction;

		vRampColor[0] = min( 1.0f, vRampColor[0] );
		vRampColor[1] = min( 1.0f, vRampColor[1] );
		vRampColor[2] = min( 1.0f, vRampColor[2] );

		float sinLifetime = sin(pParticle->m_Lifetime * 3.14159f / pParticle->m_DieTime);

		if ( m_nType == STEAM_HEATWAVE )
		{
			RenderParticle_ColorSizePerturbNormal(
				pIterator->GetParticleDraw(),
				tPos,
				vRampColor,
				sinLifetime * (m_clrRender->a/255.0f),
				FLerp(m_StartSize, m_EndSize, pParticle->m_Lifetime));
		}
		else
		{
			RenderParticle_ColorSizeAngle(
				pIterator->GetParticleDraw(),
				tPos,
				vRampColor,
				sinLifetime * (m_clrRender->a/255.0f),
				FLerp(pParticle->m_uchStartSize, pParticle->m_uchEndSize, pParticle->m_Lifetime),
				pParticle->m_flRoll );
		}

		pParticle = (const SteamJetParticle*)pIterator->GetNext( sortKey );
	}
}
Example #6
0
void C_AR2Explosion::RenderParticles( CParticleRenderIterator *pIterator )
{
	const AR2ExplosionParticle *pParticle = (const AR2ExplosionParticle *)pIterator->GetFirst();
	while ( pParticle )
	{
		float sortKey = 0;
		if ( pParticle->m_Lifetime >= 0.0f )
		{
			// Draw.
			float lifetimePercent = ( pParticle->m_Lifetime - AR2_DUST_FADE_IN_TIME ) / pParticle->m_Dwell;

			// FIXME: base color should be a dirty version of the material color
			Vector color = g_AR2DustColor1 * (1.0 - lifetimePercent) + g_AR2DustColor2 * lifetimePercent;
			
			Vector tPos;
			TransformParticle(m_pParticleMgr->GetModelView(), pParticle->m_Pos, tPos);
			sortKey = tPos.z;
			
			float	alpha;

			if ( pParticle->m_Lifetime < AR2_DUST_FADE_IN_TIME )
			{
				alpha = AR2_DUST_ALPHA * ( pParticle->m_Lifetime / AR2_DUST_FADE_IN_TIME );
			}
			else
			{
				alpha = AR2_DUST_ALPHA * ( 1.0f - lifetimePercent );
			}

			alpha *= GetAlphaDistanceFade( tPos, IsXbox() ? 100 : 50, IsXbox() ? 200 : 150 );

			RenderParticle_ColorSizeAngle(
				pIterator->GetParticleDraw(),
				tPos,
				color,
				alpha,
				pParticle->m_Dist, // size based on how far it's traveled
				pParticle->m_Roll);
		}

		pParticle = (const AR2ExplosionParticle *)pIterator->GetNext( sortKey );
	}
}
//-----------------------------------------------------------------------------
// Purpose: Simulate and render the particle in this system
// Input  : *pInParticle - particle to consider
//			*pDraw - drawing utilities
//			&sortKey - sorting key
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CFleckParticles::SimulateAndRender( Particle *pInParticle, ParticleDraw *pDraw, float &sortKey )
{
	FleckParticle *pParticle = (FleckParticle *) pInParticle;

	const float	timeDelta = pDraw->GetTimeDelta();

	//Should this particle die?
	pParticle->m_flLifetime += timeDelta;

	if ( pParticle->m_flLifetime >= pParticle->m_flDieTime )
		return false;
	
	//Render
	Vector	tPos;

	TransformParticle( g_ParticleMgr.GetModelView(), pParticle->m_Pos, tPos );
	sortKey = (int) tPos.z;
	
	Vector	color;
	color[0] = pParticle->m_uchColor[0] / 255.0f;
	color[1] = pParticle->m_uchColor[1] / 255.0f;
	color[2] = pParticle->m_uchColor[2] / 255.0f;

	pParticle->m_flRoll += pParticle->m_flRollDelta * timeDelta;

	//Render it
	RenderParticle_ColorSizeAngle(
		pDraw,
		tPos,
		color,
		1.0f - (pParticle->m_flLifetime / pParticle->m_flDieTime),
		pParticle->m_uchSize,
		pParticle->m_flRoll );

	//Simulate the movement with collision
	trace_t trace;
	m_ParticleCollision.MoveParticle( pParticle->m_Pos, pParticle->m_vecVelocity, &pParticle->m_flRollDelta, timeDelta, &trace );

	return true;
}
bool C_FuncSmokeVolume::SimulateAndRender( Particle *pBaseParticle, ParticleDraw *pDraw, float &sortKey )
{
	if( m_CurrentDensity == 0.0f )
	{
		return true;
	}
	SmokeGrenadeParticle* pParticle = (SmokeGrenadeParticle*)pBaseParticle;

	Vector renderPos = pParticle->m_Pos;

	// Fade out globally.
	float alpha = m_CurrentDensity;

	pParticle->m_CurRotation += pParticle->m_RotationFactor * ( M_PI / 180.0f ) * m_RotationSpeed * pDraw->GetTimeDelta();

	// Apply the precalculated fade alpha from world geometry.
	alpha *= pParticle->m_FadeAlpha;
	
	// TODO: optimize this whole routine!
	Vector color = m_MinColor + (m_MaxColor - m_MinColor) * (pParticle->m_ColorInterp / 255.1f);
	color.x *= pParticle->m_Color[0] / 255.0f;
	color.y *= pParticle->m_Color[1] / 255.0f;
	color.z *= pParticle->m_Color[2] / 255.0f;
	
	Vector tRenderPos;
	TransformParticle( g_ParticleMgr.GetModelView(), renderPos, tRenderPos );
	sortKey = tRenderPos.z;

	RenderParticle_ColorSizeAngle(
		pDraw,
		tRenderPos,
		color,
		alpha * GetAlphaDistanceFade(tRenderPos, 10, 30),	// Alpha
		m_ParticleRadius,
		pParticle->m_CurRotation
		);

	return true;
}
void CSimpleEmitter::RenderParticles( CParticleRenderIterator *pIterator )
{
	const SimpleParticle *pParticle = (const SimpleParticle *)pIterator->GetFirst();
	while ( pParticle )
	{
		//Render
		Vector	tPos;

		TransformParticle( ParticleMgr()->GetModelView(), pParticle->m_Pos, tPos );
		float sortKey = (int) tPos.z;

		//Render it
		RenderParticle_ColorSizeAngle(
			pIterator->GetParticleDraw(),
			tPos,
			UpdateColor( pParticle ),
			UpdateAlpha( pParticle ) * GetAlphaDistanceFade( tPos, m_flNearClipMin, m_flNearClipMax ),
			UpdateScale( pParticle ),
			pParticle->m_flRoll
			);

		pParticle = (const SimpleParticle *)pIterator->GetNext( sortKey );
	}
}
bool C_AR2Explosion::SimulateAndRender(Particle *pBaseParticle, ParticleDraw *pDraw, float &sortKey)
{
	AR2ExplosionParticle* pParticle = (AR2ExplosionParticle*)pBaseParticle;

	float dt = pDraw->GetTimeDelta();
	if (dt > 0.05)
		dt = 0.05; // yuck, air resistance function craps out at less then 20fps

	// Update its lifetime.
	pParticle->m_Lifetime += dt; // pDraw->GetTimeDelta();
	if(pParticle->m_Lifetime > pParticle->m_Dwell)
	{
		// faded to nothing....
		return false;
	}

	// Spin the thing
	pParticle->m_Roll += pParticle->m_RollSpeed * pDraw->GetTimeDelta();

	// delayed?
	if(pParticle->m_Lifetime < 0.0f)
	{
		// not alive yet
		return true;
	}

	// Draw.
	float lifetimePercent = ( pParticle->m_Lifetime - AR2_DUST_FADE_IN_TIME ) / pParticle->m_Dwell;

	// FIXME: base color should be a dirty version of the material color
	Vector color = g_AR2DustColor1 * (1.0 - lifetimePercent) + g_AR2DustColor2 * lifetimePercent;
	
	Vector tPos;
	TransformParticle(m_pParticleMgr->GetModelView(), pParticle->m_Pos, tPos);
	// sortKey = tPos.z;
	
	float	alpha;

	if ( pParticle->m_Lifetime < AR2_DUST_FADE_IN_TIME )
	{
		alpha = AR2_DUST_ALPHA * ( pParticle->m_Lifetime / AR2_DUST_FADE_IN_TIME );
	}
	else
	{
		alpha = AR2_DUST_ALPHA * ( 1.0f - lifetimePercent );
	}

	alpha *= GetAlphaDistanceFade( tPos, 50, 150 );

	RenderParticle_ColorSizeAngle(
		pDraw,
		tPos,
		color,
		alpha,
		pParticle->m_Dist, // size based on how far it's traveled
		pParticle->m_Roll);

	// Move it (this comes after rendering to make it clear that moving the particle here won't change
	// its rendering for this frame since m_TransformedPos has already been set).
	pParticle->m_Pos = pParticle->m_Pos + pParticle->m_Velocity * dt;

	// keep track of distance traveled
	pParticle->m_Dist = pParticle->m_Dist + pParticle->m_Velocity.Length() * dt;

	// Dampen velocity.
	float dist = pParticle->m_Velocity.Length()	* dt;
	float r = dist * dist;
	// FIXME: this is a really screwy air-resistance function....
	pParticle->m_Velocity = pParticle->m_Velocity * (100 / (100 + r )); 

	// dampen roll
	static float dtime;
	static float decay;
	if (dtime != dt)
	{
		dtime = dt;
		decay = ExponentialDecay( 0.3, 1.0, dtime );
	}
	if (fabs(pParticle->m_RollSpeed) > 0.2)
		pParticle->m_RollSpeed = pParticle->m_RollSpeed * decay;

	return true;
}
//-----------------------------------------------------------------------------
// Purpose: Update state + render
//-----------------------------------------------------------------------------
bool CBasePlasmaProjectile::SimulateAndRender(Particle *pInParticle, ParticleDraw *pDraw, float &sortKey)
{
	if ( IsDormantPredictable() )
		return true;

	if ( GetMoveType() == MOVETYPE_NONE )
		return true;

	// Update the particle position
	pInParticle->m_Pos = GetAbsOrigin();

	// Add our blended offset
	if ( gpGlobals->curtime < m_Shared.GetSpawnTime() + REMAP_BLEND_TIME )
	{
		float frac = ( gpGlobals->curtime - m_Shared.GetSpawnTime() ) / REMAP_BLEND_TIME;
		frac = 1.0f - clamp( frac, 0.0f, 1.0f );
		Vector scaledOffset;
		VectorScale( m_vecGunOriginOffset, frac, scaledOffset );
		pInParticle->m_Pos += scaledOffset;
	}

	float timeDelta = pDraw->GetTimeDelta();

	// Render the head particle
	if ( pInParticle == m_pHeadParticle )
	{
		SimpleParticle *pParticle = (SimpleParticle *) pInParticle;
		pParticle->m_flLifetime += timeDelta;

		// Render
		Vector tPos, vecOrigin;
		RemapPosition( m_pPreviousPositions[MAX_HISTORY-1].m_Position, m_pPreviousPositions[MAX_HISTORY-1].m_Time, vecOrigin );

		TransformParticle( ParticleMgr()->GetModelView(), vecOrigin, tPos );
		sortKey = (int) tPos.z;

		//Render it
		RenderParticle_ColorSizeAngle(
			pDraw,
			tPos,
			UpdateColor( pParticle, timeDelta ),
			UpdateAlpha( pParticle, timeDelta ) * GetAlphaDistanceFade( tPos, 16, 64 ),
			UpdateScale( pParticle, timeDelta ),
			UpdateRoll( pParticle, timeDelta ) );

		/*
		if ( m_flNextSparkEffect < gpGlobals->curtime )
		{
			// Drop sparks?
			if ( GetTeamNumber() == TEAM_HUMANS )
			{
				g_pEffects->Sparks( pInParticle->m_Pos, 1, 3 );
			}
			else
			{
				g_pEffects->EnergySplash( pInParticle->m_Pos, vec3_origin );
			}
			m_flNextSparkEffect = gpGlobals->curtime + RandomFloat( 0.5, 2 );
		}
		*/

		return true;
	}

	// Render the trail
	TrailParticle *pParticle = (TrailParticle *) pInParticle;
	pParticle->m_flLifetime += timeDelta;
	Vector vecScreenStart, vecScreenDelta;
	sortKey = pParticle->m_Pos.z;

	// NOTE: We need to do everything in screen space
	float flFragmentLength = (MAX_HISTORY > 1) ? 1.0 / (float)(MAX_HISTORY-1) : 1.0;

	for ( int i = 0; i < (MAX_HISTORY-1); i++ )
	{
		Vector vecWorldStart, vecWorldEnd, vecScreenEnd;
		float flStartV, flEndV;

		// Did we just appear?
		if ( m_pPreviousPositions[i].m_Time == 0 )
			continue;

		RemapPosition( m_pPreviousPositions[i+1].m_Position, m_pPreviousPositions[i+1].m_Time, vecWorldStart );
		RemapPosition( m_pPreviousPositions[i].m_Position, m_pPreviousPositions[i].m_Time, vecWorldEnd );

		// Texture wrapping
		flStartV = (flFragmentLength * (i+1));
		flEndV = (flFragmentLength * i);
		
		TransformParticle( ParticleMgr()->GetModelView(), vecWorldStart, vecScreenStart );
		TransformParticle( ParticleMgr()->GetModelView(), vecWorldEnd, vecScreenEnd );
		Vector vecScreenDelta = (vecScreenEnd - vecScreenStart);
		if ( vecScreenDelta == vec3_origin )
			continue;

		/*
		Vector vecForward, vecRight;
		AngleVectors( MainViewAngles(), &vecForward, &vecRight, NULL );
		Vector vecWorldDelta = ( vecWorldEnd - vecWorldStart );
		VectorNormalize( vecWorldDelta );
		float flDot = fabs(DotProduct( vecWorldDelta, vecForward ));
		if ( flDot > 0.99 )
		{
			// Remap alpha
			pParticle->m_flColor[3] = 1.0 - min( 1.0, RemapVal( flDot, 0.99, 1.0, 0, 1 ) );
		}
		*/

		// See if we should fade
		float color[4];
		Color32ToFloat4( color, pParticle->m_color );
		Tracer_Draw( pDraw, vecScreenStart, vecScreenDelta, pParticle->m_flWidth, color, flStartV, flEndV );
	}

	return true;
}
void C_ParticleSmokeGrenade::RenderParticles( CParticleRenderIterator *pIterator )
{
	const SmokeGrenadeParticle *pParticle = (const SmokeGrenadeParticle*)pIterator->GetFirst();
	while ( pParticle )
	{
		Vector vWorldSpacePos = m_SmokeBasePos + pParticle->m_Pos;

		float sortKey;

		// Draw.
		float len = pParticle->m_Pos.Length();
		if ( len > m_ExpandRadius )
		{
			Vector vTemp;
			TransformParticle(ParticleMgr()->GetModelView(), vWorldSpacePos, vTemp);
			sortKey = vTemp.z;		
		}
		else
		{
			// This smooths out the growing sphere. Rather than having particles appear in one spot as the sphere
			// expands, they stay at the borders.
			Vector renderPos;
			if(len > m_ExpandRadius * 0.5f)
			{
				renderPos = m_SmokeBasePos + (pParticle->m_Pos * (m_ExpandRadius * 0.5f)) / len;
			}
			else
			{
				renderPos = vWorldSpacePos;
			}		

			// Figure out the alpha based on where it is in the sphere.
			float alpha = 1 - len / m_ExpandRadius;
			
			// This changes the ramp to be very solid in the core, then taper off.
			static float testCutoff=0.7;
			if(alpha > testCutoff)
			{
				alpha = 1;
			}
			else
			{
				// at testCutoff it's 1, at 0, it's 0
				alpha = alpha / testCutoff;
			}

			// Fade out globally.
			alpha *= m_FadeAlpha;

			// Apply the precalculated fade alpha from world geometry.
			alpha *= pParticle->m_FadeAlpha;

			// TODO: optimize this whole routine!
			Vector color = m_MinColor + (m_MaxColor - m_MinColor) * (pParticle->m_ColorInterp / 255.1f);
			color.x *= pParticle->m_Color[0] / 255.0f;
			color.y *= pParticle->m_Color[1] / 255.0f;
			color.z *= pParticle->m_Color[2] / 255.0f;

			// Lighting.
			ApplyDynamicLight( renderPos, color );
			
			Vector tRenderPos;
			TransformParticle(ParticleMgr()->GetModelView(), renderPos, tRenderPos);
			sortKey = tRenderPos.z;

			RenderParticle_ColorSizeAngle(
				pIterator->GetParticleDraw(),
				tRenderPos,
				color,
				alpha * GetAlphaDistanceFade(tRenderPos, 100, 200),	// Alpha
				SMOKEPARTICLE_SIZE,
				pParticle->m_CurRotation
				);
		}

		pParticle = (SmokeGrenadeParticle*)pIterator->GetNext( sortKey );
	}
}
bool C_ParticleSmokeGrenade::SimulateAndRender(Particle *pBaseParticle, ParticleDraw *pDraw, float &sortKey)
{
	SmokeGrenadeParticle* pParticle = (SmokeGrenadeParticle*)pBaseParticle;

	// Draw.
	float len = (pParticle->m_Pos - m_SmokeBasePos).Length();
	if(len > m_ExpandRadius)
	{
		Vector vTemp;
		TransformParticle(g_ParticleMgr.GetModelView(), pParticle->m_Pos, vTemp);
		sortKey = vTemp.z;		
		return true;
	}

	// This smooths out the growing sphere. Rather than having particles appear in one spot as the sphere
	// expands, they stay at the borders.
	Vector renderPos;
	if(len > m_ExpandRadius * 0.5f)
	{
		renderPos = m_SmokeBasePos + ((pParticle->m_Pos - m_SmokeBasePos) * (m_ExpandRadius * 0.5f)) / len;
	}
	else
	{
		renderPos = pParticle->m_Pos;
	}		

	// Figure out the alpha based on where it is in the sphere.
	float alpha = 1 - len / m_ExpandRadius;
	
	// This changes the ramp to be very solid in the core, then taper off.
	static float testCutoff=0.7;
	if(alpha > testCutoff)
	{
		alpha = 1;
	}
	else
	{
		// at testCutoff it's 1, at 0, it's 0
		alpha = alpha / testCutoff;
	}

	// Fade out globally.
	alpha *= m_FadeAlpha;


	pParticle->m_CurRotation += pParticle->m_RotationSpeed * pDraw->GetTimeDelta();

	// Apply the precalculated fade alpha from world geometry.
	alpha *= pParticle->m_FadeAlpha;
	
	// TODO: optimize this whole routine!
	Vector color = m_MinColor + (m_MaxColor - m_MinColor) * (pParticle->m_ColorInterp / 255.1f);
	color.x *= pParticle->m_Color[0] / 255.0f;
	color.y *= pParticle->m_Color[1] / 255.0f;
	color.z *= pParticle->m_Color[2] / 255.0f;
	
	Vector tRenderPos;
	TransformParticle(g_ParticleMgr.GetModelView(), renderPos, tRenderPos);
	sortKey = tRenderPos.z;

	RenderParticle_ColorSizeAngle(
		pDraw,
		tRenderPos,
		color,
		alpha * GetAlphaDistanceFade(tRenderPos, 10, 30),	// Alpha
		SMOKEPARTICLE_SIZE,
		pParticle->m_CurRotation
		);

	return true;
}