#include "cbase.h" #include "c_asw_mesh_emitter_entity.h" #include "c_asw_generic_emitter.h" #include "KeyValues.h" #include #include "gamestringpool.h" #include "c_tracer.h" #include "precache_register.h" #include "asw_shareddefs.h" #include "tier0/vprof.h" #include "datacache/imdlcache.h" #include "engine/IVDebugOverlay.h" #include "soundemittersystem/isoundemittersystembase.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" ConVar asw_mesh_emitter_draw("asw_mesh_emitter_draw", "1", FCVAR_CHEAT, "Draw meshes from mesh emitters"); ConVar asw_emitter_min_collision_speed("asw_emitter_min_collision_speed", "50", FCVAR_CHEAT, "Minimum speed to make a sound"); ConVar asw_emitter_max_collision_speed("asw_emitter_max_collision_speed", "80", FCVAR_CHEAT, "Maximum speed that makes a full volume"); CASWGenericEmitter::CASWGenericEmitter( const char *pDebugName ) : CSimpleEmitter( pDebugName ) { m_iResetEmitter = 0; m_ParticlesPerSecond = 1; m_tParticleTimer.Init( m_ParticlesPerSecond ); m_CurrentParticlesPerSecond = m_ParticlesPerSecond; m_hMaterial = GetPMaterial( "effects/yellowflare" ); m_hGlowMaterial = NULL; m_bEmit = true; m_szGlowMaterialName[0] = '\0'; m_fGlowScale = 1.7f; Q_snprintf(m_szMaterialName, sizeof(m_szTemplateName), "effects/yellowflare"); Q_snprintf(m_szTemplateName, sizeof(m_szTemplateName), "None"); Q_snprintf(m_szCollisionTemplateName, sizeof(m_szCollisionTemplateName), "None"); Q_snprintf(m_szDropletTemplateName, sizeof(m_szDropletTemplateName), "None"); m_Colors[0].bUse = true; m_Colors[0].fTime = 0; m_Colors[0].fBandLength = 100; m_Colors[0].Color.r = 255; m_Colors[0].Color.g = 255; m_Colors[0].Color.b = 255; m_Scales[0].bUse = true; m_Scales[0].fTime = 0; m_Scales[0].fBandLength = 100; m_Scales[0].fScale = 30; m_Alphas[0].bUse = true; m_Alphas[0].fTime = 0; m_Alphas[0].fBandLength = 100; m_Alphas[0].fAlpha = 255; for (int i=1;i<5;i++) { m_Colors[i].bUse = false; m_Colors[i].fTime = 0; m_Colors[i].fBandLength = 0; m_Colors[i].Color.r = 0; m_Colors[i].Color.g = 0; m_Colors[i].Color.b = 0; m_Scales[i].bUse = false; m_Scales[i].fTime = 0; m_Scales[i].fBandLength = 0; m_Scales[i].fScale = 0; m_Alphas[i].bUse = false; m_Alphas[i].fTime = 0; m_Alphas[i].fBandLength = 0; m_Alphas[i].fAlpha = 0; } m_fParticleLifeMin = 3; m_fParticleLifeMax = 5; m_fPresimulateTime = 0; velocityMin.Init(-30,-30,0); velocityMax.Init(30,30,0); positionMin.Init(); positionMax.Init(); accelerationMin.Init(); accelerationMax.Init(); fGravity = 0; fRollMin = 0; fRollMax = 0; fRollDeltaMin = 0; fRollDeltaMax = 0; m_fEmitterScale = 1.0f; m_bWrapParticlesToSpawnBounds = false; m_bLocalCoordSpace = false; // if true, particles are stored in local space and transformed to the position of the emitter when rendered m_vecPosition.Init(); m_vecEmitterPositionDelta.Init(); m_vecLastSimulatePosition.Init(); m_angFacing.Init(); m_UseCollision = aswpc_none; m_hCollisionIgnoreEntity = NULL; m_fCollisionDampening = 50.0f; m_hCollisionEmitter = NULL; m_hDropletEmitter = NULL; m_DrawType = aswpdt_sprite; m_fBeamLength = 1.0f; m_bScaleBeamByVelocity = true; m_bScaleBeamByLifeLeft = true; m_iBeamPosition = ASW_EMITTER_BEAM_POS_BEHIND; m_fDropletChance = 40.f; m_fLargestParticleSize = 0; m_iParticleSupply = -1; m_iInitialParticleSupply = -1; m_fDieTime = 0; m_fLifeLostOnCollision= 0; // lighting m_iLightingType = 0; m_fLightApply = 0.5f; m_vecLighting.Init(); // custom collision m_bUseCustomCollisionMask = false; m_bUseCustomCollisionGroup = false; m_CustomCollisionMask = MASK_SOLID; m_CustomCollisionGroup = COLLISION_GROUP_NONE; m_hMeshEmitter = NULL; m_vecTraceMins = Vector(-10, -10, -10); m_vecTraceMaxs = Vector(10, 10, 10); m_bHullTraces = false; m_fReduceRollRateOnCollision = 1.0f; m_szCollisionSoundName[0] = '\0'; m_szCollisionDecalName[0] = '\0'; m_fParticleLocal = 0; } CSmartPtr CASWGenericEmitter::Create( const char *pDebugName ) { CASWGenericEmitter *pRet = new CASWGenericEmitter( pDebugName ); pRet->SetDynamicallyAllocated( true ); return pRet; } // returns the desired alpha for this particle (based upon its lifetime, dietime and our alpha nodes) float CASWGenericEmitter::UpdateAlpha( const SimpleParticle *pParticle ) { float fLightingScale = m_iLightingType < 2 ? 1.0f : // no scaling alpha by lighting ((m_vecLighting.x + m_vecLighting.y + m_vecLighting.z) / 3.0f); // scale alpha by average of rgb lighting // check for only applying some fraction of the shading fLightingScale += (1.0f - fLightingScale) * (1.0f - m_fLightApply); // find which band we're in float fTime = (pParticle->m_flLifetime / pParticle->m_flDieTime) * 100.0f; if (fTime < m_Alphas[0].fTime) { return (m_Alphas[0].fAlpha / 255.0f) * fLightingScale; } for (unsigned int i=0; i<5; i++) { if (fTime >= m_Alphas[i].fTime && (fTime - m_Alphas[i].fTime) <= m_Alphas[i].fBandLength) { if (i==4 || !m_Alphas[i+1].bUse) // last band, use the solid alpha return (m_Alphas[i].fAlpha / 255.0f) * fLightingScale; // transition = into band / band length float fTransition = (fTime - m_Alphas[i].fTime) / m_Alphas[i].fBandLength; // transition is how far to go between this band's alpha and the next band's alpha float fDifference = m_Alphas[i+1].fAlpha - m_Alphas[i].fAlpha; return ((m_Alphas[i].fAlpha + fDifference * fTransition) / 255.0f) * fLightingScale; } } return (m_Alphas[0].fAlpha / 255.0f) * fLightingScale; } float CASWGenericEmitter::ASWUpdateScale( const ASWParticle *pParticle ) { // find which band we're in float fTime = (pParticle->m_flLifetime / pParticle->m_flDieTime) * 100.0f; bool bGlow = (pParticle->m_ParticleType == aswpt_glow); if (fTime < m_Scales[0].fTime) { if (bGlow) return m_Scales[0].fScale * m_fEmitterScale * m_fGlowScale; return m_Scales[0].fScale * m_fEmitterScale; } for (unsigned int i=0; i<5; i++) { if (fTime >= m_Scales[i].fTime && (fTime - m_Scales[i].fTime) <= m_Scales[i].fBandLength) { if (i==4 || !m_Scales[i+1].bUse) // last band, use the solid fScale return m_Scales[i].fScale * m_fEmitterScale; // transition = into band / band length float fTransition = (fTime - m_Scales[i].fTime) / m_Scales[i].fBandLength; // transition is how far to go between this band's fScale and the next band's fScale float fDifference = m_Scales[i+1].fScale - m_Scales[i].fScale; if (bGlow) return (m_Scales[i].fScale + fDifference * fTransition) * m_fEmitterScale * m_fGlowScale; return (m_Scales[i].fScale + fDifference * fTransition) * m_fEmitterScale; } } if (bGlow) return m_Scales[0].fScale * m_fEmitterScale * m_fGlowScale; return m_Scales[0].fScale * m_fEmitterScale; } Vector CASWGenericEmitter::UpdateColor( const SimpleParticle *pParticle ) { bool bUseLighting = (m_iLightingType == 1) || (m_iLightingType == 3); float fLightingScaleR = bUseLighting ? m_vecLighting.x : 1.0f; float fLightingScaleG = bUseLighting ? m_vecLighting.y : 1.0f; float fLightingScaleB = bUseLighting ? m_vecLighting.z : 1.0f; fLightingScaleR += (1.0f - fLightingScaleR) * (1.0f - m_fLightApply); fLightingScaleG += (1.0f - fLightingScaleG) * (1.0f - m_fLightApply); fLightingScaleB += (1.0f - fLightingScaleB) * (1.0f - m_fLightApply); // find which band we're in float fTime = (pParticle->m_flLifetime / pParticle->m_flDieTime) * 100.0f; if (fTime < m_Colors[0].fTime) { Vector result; result[0] = (m_Colors[0].Color.r / 255.0f) * fLightingScaleR; result[1] = (m_Colors[0].Color.g / 255.0f) * fLightingScaleG; result[2] = (m_Colors[0].Color.b / 255.0f) * fLightingScaleB; return result; } for (unsigned int i=0; i<5; i++) { if (fTime >= m_Colors[i].fTime && (fTime - m_Colors[i].fTime) <= m_Colors[i].fBandLength) { if (i==4 || !m_Colors[i+1].bUse) // last band, use the solid fScale { Vector result; result[0] = (m_Colors[i].Color.r / 255.0f) * fLightingScaleR; result[1] = (m_Colors[i].Color.g / 255.0f) * fLightingScaleG; result[2] = (m_Colors[i].Color.b / 255.0f) * fLightingScaleB; return result; } // transition = into band / band length float fTransition = (fTime - m_Colors[i].fTime) / m_Colors[i].fBandLength; // transition is how far to go between this band's fScale and the next band's fScale float fDifferenceR = m_Colors[i+1].Color.r - m_Colors[i].Color.r; float fDifferenceG = m_Colors[i+1].Color.g - m_Colors[i].Color.g; float fDifferenceB = m_Colors[i+1].Color.b - m_Colors[i].Color.b; Vector result; result[0] = ((m_Colors[i].Color.r + fDifferenceR * fTransition) / 255.0f) * fLightingScaleR; result[1] = ((m_Colors[i].Color.g + fDifferenceG * fTransition) / 255.0f) * fLightingScaleG; result[2] = ((m_Colors[i].Color.b + fDifferenceB * fTransition) / 255.0f) * fLightingScaleB; return result; } } Vector result; result[0] = (m_Colors[0].Color.r / 255.0f) * fLightingScaleR; result[1] = (m_Colors[0].Color.g / 255.0f) * fLightingScaleG; result[2] = (m_Colors[0].Color.b / 255.0f) * fLightingScaleB; return result; } // precalcuate each band length (where a band is one straight line on the node graph) void CASWGenericEmitter::CalcBandLengths() { float fCurrentTime = 0; for (unsigned int i=0; i<5; i++) { if (!m_Colors[i].bUse) break; if (i==4 || !m_Colors[i+1].bUse) // last band { m_Colors[i].fBandLength = 100.0f - fCurrentTime; } else { m_Colors[i].fBandLength = m_Colors[i+1].fTime - fCurrentTime; } fCurrentTime += m_Colors[i].fBandLength; } fCurrentTime = 0; for (unsigned int i=0; i<5; i++) { if (!m_Scales[i].bUse) break; if (i==4 || !m_Scales[i+1].bUse) // last band { m_Scales[i].fBandLength = 100.0f - fCurrentTime; } else { m_Scales[i].fBandLength = m_Scales[i+1].fTime - fCurrentTime; } fCurrentTime += m_Scales[i].fBandLength; } fCurrentTime = 0; for (unsigned int i=0; i<5; i++) { if (!m_Alphas[i].bUse) break; if (i==4 || !m_Alphas[i+1].bUse) // last band { m_Alphas[i].fBandLength = 100.0f - fCurrentTime; } else { m_Alphas[i].fBandLength = m_Alphas[i+1].fTime - fCurrentTime; } fCurrentTime += m_Alphas[i].fBandLength; } } void CASWGenericEmitter::StartRender( VMatrix &effectMatrix ) { BaseClass::StartRender(effectMatrix); if (m_iResetEmitter > 0) // this gets set to 2 when someone wants the emitter reset. It ticks down once here, then all the particles are removed in the subsequent simulate, then it gets ticked down to 0 again next time. m_iResetEmitter--; } void CASWGenericEmitter::SimulateParticles( CParticleSimulateIterator *pIterator ) { float timeDelta = pIterator->GetTimeDelta(); // work out how much the emitter moved since the last simulate if (m_vecLastSimulatePosition != vec3_origin) m_vecEmitterPositionDelta = m_vecPosition - m_vecLastSimulatePosition; else m_vecEmitterPositionDelta = vec3_origin; ASWParticle *pParticle = (ASWParticle*)pIterator->GetFirst(); while ( pParticle ) { if (SimulateParticle(pParticle, timeDelta)) { if (pParticle->m_pPartner) pParticle->m_pPartner->m_pPartner = NULL; pIterator->RemoveParticle(pParticle); } pParticle = (ASWParticle*)pIterator->GetNext(); } m_vecLastSimulatePosition = m_vecPosition; } bool CASWGenericEmitter::SimulateParticle(ASWParticle* pParticle, float timeDelta) { timeDelta += pParticle->m_fExtraSimulateTime; pParticle->m_fExtraSimulateTime = 0; if (pParticle->m_ParticleType == aswpt_glow) // glow particles move in a special way, so as to stay linked to their main particle { // advance the life of the particle pParticle->m_flLifetime += timeDelta; // position it relative to its parent based on its lifetime and constant velocity if (pParticle->m_pPartner && pParticle->m_pPartner->m_pPartner == pParticle) // check it's a valid partner { pParticle->m_Pos = pParticle->m_pPartner->m_Pos + pParticle->m_vecVelocity * pParticle->m_flLifetime; } else { pParticle->m_Pos += pParticle->m_vecVelocity * timeDelta; } } else { //Update velocity ASWUpdateVelocity( pParticle, timeDelta ); Vector newPos = pParticle->m_Pos + pParticle->m_vecVelocity * timeDelta; // move the particle along with the emitter origin? newPos += m_vecEmitterPositionDelta * (m_fParticleLocal / 100.0f); // check for wrapping spawn bounds (used primarily for particle emitters attached to the camera) if (m_bWrapParticlesToSpawnBounds && !m_bLocalCoordSpace) { for (int i=0;i<2;i++) { float width = positionMax[i] - positionMin[i]; while (newPos[i] > m_vecPosition[i] + positionMax[i]) { newPos[i] -= width; } while (newPos[i] < m_vecPosition[i] + positionMin[i]) { newPos[i] += width; } } } if (m_UseCollision != aswpc_none) { trace_t trace; int mask = MASK_SOLID_BRUSHONLY; if (m_UseCollision == aswpc_all) mask = MASK_SOLID; if (m_bUseCustomCollisionMask) mask = m_CustomCollisionMask; int col_group = COLLISION_GROUP_NONE; if (m_bUseCustomCollisionGroup) col_group = m_CustomCollisionGroup; if (m_bHullTraces) { UTIL_TraceHull(pParticle->m_Pos, newPos, m_vecTraceMins, m_vecTraceMaxs, mask, m_hCollisionIgnoreEntity.Get(), col_group, &trace); } else UTIL_TraceLine(pParticle->m_Pos, newPos, mask, m_hCollisionIgnoreEntity.Get(), col_group, &trace); if ( trace.fraction >= 1.0f ) { pParticle->m_Pos = newPos; } else { // if we're going fast enough to do a sound if (m_szCollisionSoundName[0] != '\0') { float speed = pParticle->m_vecVelocity.Length(); if (speed > asw_emitter_min_collision_speed.GetFloat()) { float fVolume = 1.0f; if (speed < asw_emitter_max_collision_speed.GetFloat()) { fVolume = (speed - asw_emitter_min_collision_speed.GetFloat()) / (asw_emitter_max_collision_speed.GetFloat() - asw_emitter_min_collision_speed.GetFloat()); } CLocalPlayerFilter filter; CSoundParameters params; if ( C_BaseEntity::GetParametersForSound( m_szCollisionSoundName, params, NULL ) ) { EmitSound_t ep( params ); ep.m_flVolume = fVolume; ep.m_nChannel = CHAN_AUTO; ep.m_pOrigin = &pParticle->m_Pos; C_BaseEntity::EmitSound( filter, 0, ep ); } } } // if we're going fast enough to do a decal if ( m_szCollisionDecalName[0] != '\0' && !pParticle->bPlacedDecal ) { float speed = pParticle->m_vecVelocity.Length(); if (speed > asw_emitter_min_collision_speed.GetFloat()) { Vector diff = newPos - pParticle->m_Pos; trace_t tr; UTIL_TraceLine( pParticle->m_Pos, pParticle->m_Pos + diff * 64.0f, MASK_SOLID, m_hCollisionIgnoreEntity.Get(), COLLISION_GROUP_NONE, &tr ); UTIL_DecalTrace( &tr, m_szCollisionDecalName ); pParticle->bPlacedDecal = true; } } // reflect off the surface float proj = (pParticle->m_vecVelocity).Dot(trace.plane.normal); VectorMA( pParticle->m_vecVelocity, -proj*2, trace.plane.normal, pParticle->m_vecVelocity ); proj = (pParticle->m_vecAccn).Dot(trace.plane.normal); VectorMA( pParticle->m_vecAccn, -proj*2, trace.plane.normal, pParticle->m_vecAccn ); // dampen pParticle->m_vecAccn *= m_fCollisionDampening * 0.01f; pParticle->m_vecVelocity *= m_fCollisionDampening * 0.01f; // dampen roll rate pParticle->m_flRollDelta *= m_fReduceRollRateOnCollision; // reduce lifespan pParticle->m_flLifetime += m_fLifeLostOnCollision; if (pParticle->m_flLifetime > pParticle->m_flDieTime) pParticle->m_flLifetime = pParticle->m_flDieTime; // if we're dropping particles, drop a bunch more when we collide if (pParticle->m_ParticleType == aswpt_normal) { // if we have a collide emitter, make it spit out a particle on collision if (m_hCollisionEmitter.IsValid()) { m_hCollisionEmitter->SpawnParticle(pParticle->m_Pos, QAngle(0,0,0)); } } } } else { pParticle->m_Pos = newPos; } //Should this particle die? pParticle->m_flLifetime += timeDelta; } if (m_iResetEmitter > 0) { pParticle->m_flLifetime = pParticle->m_flDieTime; } UpdateRoll( pParticle, timeDelta ); // Droplet Particles? if (m_hDropletEmitter.IsValid() && pParticle->m_fDropTime > 0 && pParticle->m_fDropTime <= pParticle->m_flLifetime) { if (m_fDropletChance < 0) // negative droplet chance indicates we want a droplet every interval, instead of 1 random droplet { pParticle->m_fDropTime += -m_fDropletChance; // spawn another droplet next interval } else pParticle->m_fDropTime = 0; m_hDropletEmitter->SpawnParticle(pParticle->m_Pos, QAngle(0,0,0)); } return ( pParticle->m_flLifetime >= pParticle->m_flDieTime ); } ASWParticle* CASWGenericEmitter::AddASWParticle( PMaterialHandle hMaterial, const Vector &vOrigin, float flDieTime, unsigned char uchSize ) { ASWParticle *pRet = (ASWParticle*)AddParticle( sizeof( ASWParticle ), hMaterial, vOrigin ); if ( pRet ) { pRet->m_Pos = vOrigin; pRet->m_vecVelocity.Init(); pRet->m_vecAccn.Init(); pRet->m_flRoll = 0; pRet->m_flRollDelta = 0; pRet->m_flLifetime = 0; pRet->m_flDieTime = flDieTime; pRet->m_uchColor[0] = pRet->m_uchColor[1] = pRet->m_uchColor[2] = 0; pRet->m_uchStartAlpha = pRet->m_uchEndAlpha = 255; pRet->m_uchStartSize = pRet->m_uchEndSize = uchSize; pRet->m_iFlags = 0; pRet->m_fExtraSimulateTime = 0; pRet->m_ParticleType = aswpt_normal; pRet->m_fDropTime = 0; pRet->m_pPartner = NULL; pRet->bPlacedDecal = false; } return pRet; } void CASWGenericEmitter::ASWUpdateVelocity( ASWParticle *pParticle, float timeDelta ) { // add particle acceleration to its velocity pParticle->m_vecVelocity += pParticle->m_vecAccn * timeDelta; // add gravity pParticle->m_vecVelocity.z += fGravity * timeDelta; BaseClass::UpdateVelocity(pParticle, timeDelta); } ASWParticle* CASWGenericEmitter::SpawnParticle(const Vector& Position, const QAngle& Angle) { ASWParticle *pParticle; matrix3x4_t matrix; AngleMatrix( Angle, matrix ); Vector v, pos; v.x = (positionMin.x + (positionMax.x - positionMin.x) * random->RandomFloat()) * m_fEmitterScale; v.y = (positionMin.y + (positionMax.y - positionMin.y) * random->RandomFloat()) * m_fEmitterScale; v.z = (positionMin.z + (positionMax.z - positionMin.z) * random->RandomFloat()) * m_fEmitterScale; if (m_bLocalCoordSpace) { pos = v; } else { VectorTransform(v, matrix, pos); pos += Position; } // Create the particle pParticle = AddASWParticle( m_hMaterial, pos ); if ( pParticle == NULL ) return NULL; // Setup our roll pParticle->m_flRoll = DEG2RAD(fRollMin) + (DEG2RAD(fRollMax) - DEG2RAD(fRollMin)) * random->RandomFloat(); pParticle->m_flRollDelta = DEG2RAD(fRollDeltaMin) + (DEG2RAD(fRollDeltaMax) - DEG2RAD(fRollDeltaMin)) * random->RandomFloat(); // Set our velocity v.x = (velocityMin.x + (velocityMax.x - velocityMin.x) * random->RandomFloat()) * m_fEmitterScale; v.y = (velocityMin.y + (velocityMax.y - velocityMin.y) * random->RandomFloat()) * m_fEmitterScale; v.z = (velocityMin.z + (velocityMax.z - velocityMin.z) * random->RandomFloat()) * m_fEmitterScale; VectorTransform(v, matrix, pParticle->m_vecVelocity); // set our acceleration v.x = (accelerationMin.x + (accelerationMax.x - accelerationMin.x) * random->RandomFloat()) * m_fEmitterScale; v.y = (accelerationMin.y + (accelerationMax.y - accelerationMin.y) * random->RandomFloat()) * m_fEmitterScale; v.z = (accelerationMin.z + (accelerationMax.z - accelerationMin.z) * random->RandomFloat()) * m_fEmitterScale; VectorTransform(v, matrix, pParticle->m_vecAccn); // Die in a short range of time pParticle->m_flDieTime = m_fParticleLifeMin + (m_fParticleLifeMax - m_fParticleLifeMin) * random->RandomFloat(); // alpha, color, scale are set before rendering from the emitter calcs return pParticle; } ASWParticle* CASWGenericEmitter::SpawnGlowParticle(const Vector& Position, const QAngle& Angle, ASWParticle* pParent) { if (!pParent || *GetGlowMaterial()==NULL) return NULL; ASWParticle *pParticle; // Create the particle pParticle = AddASWParticle( m_hGlowMaterial, pParent->m_Pos ); if ( pParticle == NULL ) return NULL; pParticle->m_ParticleType = aswpt_glow; // Setup our roll pParticle->m_flRoll = pParent->m_flRoll; pParticle->m_flRollDelta = pParent->m_flRollDelta; // Set our velocity/accn/etc float f = pParent->m_vecVelocity.Length() * (m_fGlowDeviation / 100); // 0.05f; pParticle->m_vecVelocity = RandomVector(-f, f); pParticle->m_vecAccn.Init();// = pParent->m_vecAccn; pParticle->m_flDieTime = pParent->m_flDieTime; // link the glow particle to its parent pParticle->m_pPartner = pParent; pParent->m_pPartner = pParticle; // alpha, color, scale are set before rendering from the emitter calcs return pParticle; } // simulate the emitter running for x seconds, instantly void CASWGenericEmitter::DoPresimulate(const Vector& Position, const QAngle& Angle) { float fTimeLeft = m_fPresimulateTime; while (fTimeLeft > 0) { fTimeLeft -= 1.0f / m_ParticlesPerSecond; if (fTimeLeft > 0 && (m_iParticleSupply == -1 || m_iParticleSupply > 0)) { ASWParticle* pParticle = SpawnParticle(Position, Angle); if (pParticle) { pParticle->m_fExtraSimulateTime = fTimeLeft; if (m_iParticleSupply > 0) m_iParticleSupply--; if (*GetGlowMaterial()!=NULL && pParticle) { ASWParticle* pGlowParticle = SpawnGlowParticle(Position, Angle, pParticle); if (pGlowParticle) pGlowParticle->m_fExtraSimulateTime = fTimeLeft; } } } } m_tParticleTimer.Init( m_ParticlesPerSecond ); } // Change the material given to particles spat out by this emitter void CASWGenericEmitter::SetMaterial(const char* materialname ) { strcpy(m_szMaterialName, materialname); m_hMaterial = GetPMaterial( materialname ); ResetEmitter(); } void CASWGenericEmitter::SetGlowMaterial(const char* materialname ) { if (!stricmp(materialname, "None")) { m_szGlowMaterialName[0]='\0'; m_hGlowMaterial = NULL; } else { strcpy(m_szGlowMaterialName, materialname); m_hGlowMaterial = GetPMaterial( materialname ); } ResetEmitter(); } void CASWGenericEmitter::SetCustomCollisionMask(int iMask) { m_bUseCustomCollisionMask = true; m_CustomCollisionMask = iMask; } void CASWGenericEmitter::SetCustomCollisionGroup(int iColGroup) { m_bUseCustomCollisionGroup = true; m_CustomCollisionGroup = iColGroup; } // Makes all the particles get destroyed in the next simulate, and triggers the presimulate void CASWGenericEmitter::ResetEmitter() { if (m_hCollisionEmitter.IsValid()) { m_hCollisionEmitter = NULL; } if (m_hDropletEmitter.IsValid()) { m_hDropletEmitter = NULL; } // if we have a collision template, instantiate that emitter too if (stricmp(m_szCollisionTemplateName, "None") && m_szCollisionTemplateName[0]!=0) { m_hCollisionEmitter = CASWGenericEmitter::Create("CollisionTemplate"); m_hCollisionEmitter->UseTemplate(m_szCollisionTemplateName); m_hCollisionEmitter->SetActive(false); m_hCollisionEmitter->m_ParticlesPerSecond = 0; // stop it producing particles over time } if (stricmp(m_szDropletTemplateName, "None") && m_szDropletTemplateName[0]!=0) { m_hDropletEmitter = CASWGenericEmitter::Create("DropletTemplate"); m_hDropletEmitter->UseTemplate(m_szDropletTemplateName); m_hDropletEmitter->SetActive(false); m_hDropletEmitter->m_ParticlesPerSecond = 0; // stop it producing particles over time } // set particle cull radius m_fLargestParticleSize = 1.0f; for (int i=0;i<5;i++) { if (m_Scales[i].bUse && m_Scales[0].fScale > m_fLargestParticleSize) m_fLargestParticleSize = m_Scales[0].fScale; } SetParticleCullRadius(m_fLargestParticleSize * m_fEmitterScale); m_iResetEmitter = 2; m_iParticleSupply = m_iInitialParticleSupply; } // this should be called when something else changes our variables void CASWGenericEmitter::Update() { CalcBandLengths(); } void CASWGenericEmitter::Think(float deltaTime, const Vector& Position, const QAngle& Angle) { VPROF_BUDGET( "CASWGenericEmitter::Think", VPROF_BUDGETGROUP_ASW_CLIENT ); // store position and facing (used by rendering when particles are stored in local space) m_vecPosition = Position; m_angFacing = Angle; // calculate the max bbox for this emitter // todo: rotate by angle, take into account gravity + acceleration //Vector PosMax = Position + velocityMax * m_fParticleLifeMax + positionMax; //Vector PosMin = Position + velocityMin * m_fParticleLifeMax + positionMin; //m_ParticleEffect.SetBBox( PosMin, PosMax ); if (m_iResetEmitter && GetBinding().GetNumActiveParticles()==0) { m_iResetEmitter = 0; // calc lighting if (m_iLightingType > 0) { m_vecLighting = engine->GetLightForPoint(Position, true); m_vecLighting.x = LinearToTexture( m_vecLighting.x ) / 255.0f; m_vecLighting.y = LinearToTexture( m_vecLighting.y ) / 255.0f; m_vecLighting.z = LinearToTexture( m_vecLighting.z ) / 255.0f; //Msg("Baked lighting for emitter: %f, %f, %f\n", m_vecLighting.x, m_vecLighting.y, m_vecLighting.z); } DoPresimulate(Position, Angle); } float curTime = gpGlobals->frametime; // Add as many particles as required this frame while ( m_tParticleTimer.NextEvent( curTime ) ) { if (m_bEmit && (m_iParticleSupply == -1 || m_iParticleSupply > 0)) { ASWParticle* pParticle = SpawnParticle(Position, Angle); if (pParticle) { if (m_iParticleSupply > 0) m_iParticleSupply--; if (curTime > 0) pParticle->m_fExtraSimulateTime = curTime; if (*GetGlowMaterial()!=NULL) { ASWParticle* pGlow = SpawnGlowParticle(Position, Angle, pParticle); if (pGlow) pGlow->m_fExtraSimulateTime = pParticle->m_fExtraSimulateTime; } if (m_hDropletEmitter.IsValid()) { if (m_fDropletChance < 0) // negative droplet chance indicates we want a droplet every interval, instead of 1 random droplet { pParticle->m_fDropTime = -m_fDropletChance; } else if (random->RandomFloat() < (m_fDropletChance / 100.0f)) pParticle->m_fDropTime = random->RandomFloat(0.25f, 1.0f) * pParticle->m_flDieTime; } } } } if (m_CurrentParticlesPerSecond != m_ParticlesPerSecond) { m_tParticleTimer.Init( m_ParticlesPerSecond ); m_CurrentParticlesPerSecond = m_ParticlesPerSecond; } if (m_hCollisionEmitter.IsValid()) { m_hCollisionEmitter->Think(deltaTime, Position, Angle); } if (m_hDropletEmitter.IsValid()) { m_hDropletEmitter->Think(deltaTime, Position, Angle); } if (m_fDieTime > 0 && m_fDieTime < gpGlobals->curtime) { m_bEmit = false; // ASWTODO - find out a good way to destroy this CParticleEffect //Release(); } } void CASWGenericEmitter::SetActive(bool b) { if (!IsReleased()) // don't change our active state if we're dying m_bEmit = b; } void CASWGenericEmitter::RenderParticles( CParticleRenderIterator *pIterator ) { /* if (asw_mesh_emitter_draw.GetBool()) { // check for drawing by some parent entity mesh - is this going to mess up draw order and things? if ( m_DrawType == aswpdt_mesh && m_hMeshEmitter.Get()) { MDLCACHE_CRITICAL_SECTION(); if (m_hMeshEmitter->PrepareToDraw()) { const ASWParticle *pParticle = (const ASWParticle *)pIterator->GetFirst(); int iDrawn = 0; while ( pParticle ) { //Render Vector tPos; Vector vecParticlePos = pParticle->m_Pos; // if particles are stored in local coord space, then transform them to the particle system's last known coords //if (m_bLocalCoordSpace) //{ //matrix3x4_t matrix; //AngleMatrix( m_angFacing, matrix ); //VectorTransform(pParticle->m_Pos, matrix, vecParticlePos); //vecParticlePos += m_vecPosition; //} debugoverlay->AddLineOverlay( vecParticlePos, vecParticlePos+Vector(3, 3, 3), 0, 0, 255, true, 0.01f ); iDrawn += m_hMeshEmitter->DrawParticle(vecParticlePos); TransformParticle( ParticleMgr()->GetModelView(), vecParticlePos, tPos ); float sortKey = (int) tPos.z; pParticle = (const ASWParticle *)pIterator->GetNext( sortKey ); } Msg("Drew mesh particles: %d\n", iDrawn); } return; } }*/ const ASWParticle *pParticle = (const ASWParticle *)pIterator->GetFirst(); while ( pParticle ) { //Render Vector tPos; Vector vecParticlePos = pParticle->m_Pos; // if particles are stored in local coord space, then transform them to the particle system's last known coords if (m_bLocalCoordSpace) { matrix3x4_t matrix; AngleMatrix( m_angFacing, matrix ); VectorTransform(pParticle->m_Pos, matrix, vecParticlePos); vecParticlePos += m_vecPosition; } TransformParticle( ParticleMgr()->GetModelView(), vecParticlePos, tPos ); float sortKey = (int) tPos.z; if (m_DrawType == aswpdt_sprite) { //Render it as a normal sprite Vector col = UpdateColor( pParticle ); RenderParticle_ColorSizeAngle( pIterator->GetParticleDraw(), tPos, col, //UpdateColor( pParticle ), UpdateAlpha( pParticle ) * GetAlphaDistanceFade( tPos, m_flNearClipMin, m_flNearClipMax ), ASWUpdateScale( pParticle ), pParticle->m_flRoll ); } else if (m_DrawType == aswpdt_tracer) // beam drawing { // render the particle as a tracer beam float fSize = ASWUpdateScale( pParticle ); float lifePerc = 1.0f - ( pParticle->m_flLifetime / pParticle->m_flDieTime ); float scale = fSize * 0.1; if (m_bScaleBeamByLifeLeft) scale *= lifePerc; if ( scale < 0.01f ) scale = 0.01f; Vector delta; Vector3DMultiply( ParticleMgr()->GetModelView(), pParticle->m_vecVelocity, delta ); float flLength = (pParticle->m_vecVelocity * scale).Length();//( delta - pos ).Length(); float flWidth = ( flLength < fSize ) ? flLength : fSize; float color[4]; Vector col = UpdateColor( pParticle ); color[0] = col[0]; color[1] = col[1]; color[2] = col[2]; color[3] = UpdateAlpha( pParticle ) * GetAlphaDistanceFade( tPos, m_flNearClipMin, m_flNearClipMax ); if (!m_bScaleBeamByVelocity) { VectorNormalize(delta); } if (m_iBeamPosition == ASW_EMITTER_BEAM_POS_FRONT) { tPos += (delta*scale)*m_fBeamLength; } else if (m_iBeamPosition == ASW_EMITTER_BEAM_POS_CENTER) { tPos += (delta*scale)*m_fBeamLength*0.5f; } Tracer_Draw( pIterator->GetParticleDraw(), tPos, -(delta*scale)*m_fBeamLength, flWidth, color ); } pParticle = (const ASWParticle *)pIterator->GetNext( sortKey ); } } void CASWGenericEmitter::SetCollisionTemplate(const char* templatename ) { strcpy(m_szCollisionTemplateName, templatename); ResetEmitter(); } void CASWGenericEmitter::SetDropletTemplate(const char* templatename ) { strcpy(m_szDropletTemplateName, templatename); ResetEmitter(); } void CASWGenericEmitter::SetCollisionSound(const char* szSoundName ) { strcpy(m_szCollisionSoundName, szSoundName); } void CASWGenericEmitter::SetCollisionDecal(const char* szDecalName ) { strcpy(m_szCollisionDecalName, szDecalName); } void CASWGenericEmitter::SetDieTime(float fTime) { m_fDieTime = fTime; } // load our emitter settings from a particular template void CASWGenericEmitter::UseTemplate(const char* templatename, bool bReset, bool bLoadFromCache) { char buf[MAX_PATH]; Q_snprintf(buf, sizeof(buf), "resource/particletemplates/%s.ptm", templatename); KeyValues* kv = NULL; if (bLoadFromCache) kv = g_ASWGenericEmitterCache.FindTemplate(templatename); else { Msg("UseTemplate uncache force\n"); kv = new KeyValues("UncachedParticleEmitter"); if ( !kv->LoadFromFile(filesystem, buf, "GAME") ) { DevMsg( 1, "C_ASW_Emitter::UseTemplate: couldn't load file: %s\n", buf ); return; } } // couldn't find the template if ( !kv ) { DevMsg( 1, "Couldn't load emitter template from cache. %s\n", buf ); return; } strcpy(m_szTemplateName, templatename); strcpy(m_szMaterialName, kv->GetString("Material")); m_hMaterial = GetPMaterial( m_szMaterialName ); strcpy(m_szGlowMaterialName, kv->GetString("GlowMaterial")); if (Q_strlen(m_szGlowMaterialName) <= 0) m_hGlowMaterial = NULL; else m_hGlowMaterial = GetPMaterial( m_szGlowMaterialName ); m_ParticlesPerSecond = kv->GetFloat("ParticlesPerSecond"); m_fParticleLifeMin = kv->GetFloat("ParticleLifeMin"); m_fParticleLifeMax = kv->GetFloat("ParticleLifeMax"); m_fPresimulateTime = kv->GetFloat("PresimulateTime"); fRollMin = kv->GetFloat("RollMin"); fRollMax = kv->GetFloat("RollMax"); fRollDeltaMin = kv->GetFloat("RollDeltaMin"); fRollDeltaMax = kv->GetFloat("RollDeltaMax"); velocityMin.x = kv->GetFloat("VelocityMinX"); velocityMin.y = kv->GetFloat("VelocityMinY"); velocityMin.z = kv->GetFloat("VelocityMinZ"); velocityMax.x = kv->GetFloat("VelocityMaxX"); velocityMax.y = kv->GetFloat("VelocityMaxY"); velocityMax.z = kv->GetFloat("VelocityMaxZ"); positionMin.x = kv->GetFloat("PositionMinX"); positionMin.y = kv->GetFloat("PositionMinY"); positionMin.z = kv->GetFloat("PositionMinZ"); positionMax.x = kv->GetFloat("PositionMaxX"); positionMax.y = kv->GetFloat("PositionMaxY"); positionMax.z = kv->GetFloat("PositionMaxZ"); accelerationMin.x = kv->GetFloat("AccnMinX"); accelerationMin.y = kv->GetFloat("AccnMinY"); accelerationMin.z = kv->GetFloat("AccnMinZ"); accelerationMax.x = kv->GetFloat("AccnMaxX"); accelerationMax.y = kv->GetFloat("AccnMaxY"); accelerationMax.z = kv->GetFloat("AccnMaxZ"); fGravity = kv->GetFloat("Gravity"); m_fCollisionDampening = kv->GetFloat("CollisionDampening", 50.0f); m_fBeamLength = kv->GetFloat("BeamLength", 1.0f); m_bScaleBeamByVelocity = kv->GetInt("ScaleBeamByVelocity", 1) != 0; m_bScaleBeamByLifeLeft = kv->GetInt("ScaleBeamByLifeLeft", 1) != 0; m_iBeamPosition = kv->GetInt("BeamPosition"); m_fGlowScale = kv->GetFloat("GlowScale", 1.7f); m_fGlowDeviation = kv->GetFloat("GlowDeviation", 5); m_fDropletChance = kv->GetFloat("DropletChance"); m_fParticleLocal = kv->GetFloat("ParticleLocal"); m_fLifeLostOnCollision = kv->GetFloat("LifeLostOnCollision"); m_iParticleSupply = m_iInitialParticleSupply = kv->GetInt("ParticleSupply", -1); m_DrawType = (ASWParticleDrawType) kv->GetInt("DrawType"); m_iLightingType = kv->GetInt("Lighting"); m_fLightApply = kv->GetFloat("LightApply"); strcpy(m_szCollisionTemplateName, kv->GetString("CollisionTemplate")); strcpy(m_szDropletTemplateName, kv->GetString("DropletTemplate")); // save color nodes for (int i=0;i<5;i++) { char buf[64]; Q_snprintf(buf, 64, "Color%dUse", i); m_Colors[i].bUse = kv->GetBool(buf); Q_snprintf(buf, 64, "Color%dTime", i); m_Colors[i].fTime = kv->GetFloat(buf); Q_snprintf(buf, 64, "Color%dRed", i); m_Colors[i].Color.r = kv->GetInt(buf); Q_snprintf(buf, 64, "Color%dGreen", i); m_Colors[i].Color.g = kv->GetInt(buf); Q_snprintf(buf, 64, "Color%dBlue", i); m_Colors[i].Color.b = kv->GetInt(buf); } // save scale nodes for (int i=0;i<5;i++) { char buf[64]; Q_snprintf(buf, 64, "Scale%dUse", i); m_Scales[i].bUse = kv->GetBool(buf); Q_snprintf(buf, 64, "Scale%dTime", i); m_Scales[i].fTime = kv->GetFloat(buf); Q_snprintf(buf, 64, "Scale%dValue", i); m_Scales[i].fScale = kv->GetFloat(buf); } // save alpha nodes for (int i=0;i<5;i++) { char buf[64]; Q_snprintf(buf, 64, "Alpha%dUse", i); m_Alphas[i].bUse = kv->GetBool(buf); Q_snprintf(buf, 64, "Alpha%dTime", i); m_Alphas[i].fTime = kv->GetFloat(buf); Q_snprintf(buf, 64, "Alpha%dValue", i); m_Alphas[i].fAlpha = kv->GetFloat(buf); } // collision m_UseCollision = (ASWParticleCollision) kv->GetInt("Collision"); strcpy(m_szCollisionSoundName, kv->GetString("CollisionSound")); strcpy(m_szCollisionDecalName, kv->GetString("CollisionDecal")); Update(); if (bReset) ResetEmitter(); } void CASWGenericEmitter::SetMeshEmitter(C_ASW_Mesh_Emitter *pMeshEmitter) { m_hMeshEmitter = pMeshEmitter; m_DrawType = aswpdt_mesh; } // save our emitter settings to a template void CASWGenericEmitter::SaveTemplateAs(const char* templatename) { char filename[MAX_PATH]; Q_snprintf(filename, sizeof(filename), "resource/particletemplates/%s.ptm", templatename); strcpy(m_szTemplateName, templatename); KeyValues* kv = new KeyValues( "ParticleTemplate" ); kv->SetString("Material", m_szMaterialName); kv->SetString("GlowMaterial", m_szGlowMaterialName); kv->SetFloat("ParticlesPerSecond", m_ParticlesPerSecond); kv->SetFloat("ParticleLifeMin", m_fParticleLifeMin); kv->SetFloat("ParticleLifeMax", m_fParticleLifeMax); kv->SetFloat("PresimulateTime", m_fPresimulateTime); kv->SetFloat("RollMin", fRollMin); kv->SetFloat("RollMax", fRollMax); kv->SetFloat("RollDeltaMin", fRollDeltaMin); kv->SetFloat("RollDeltaMax", fRollDeltaMax); kv->SetFloat("VelocityMinX", velocityMin.x); kv->SetFloat("VelocityMinY", velocityMin.y); kv->SetFloat("VelocityMinZ", velocityMin.z); kv->SetFloat("VelocityMaxX", velocityMax.x); kv->SetFloat("VelocityMaxY", velocityMax.y); kv->SetFloat("VelocityMaxZ", velocityMax.z); kv->SetFloat("PositionMinX", positionMin.x); kv->SetFloat("PositionMinY", positionMin.y); kv->SetFloat("PositionMinZ", positionMin.z); kv->SetFloat("PositionMaxX", positionMax.x); kv->SetFloat("PositionMaxY", positionMax.y); kv->SetFloat("PositionMaxZ", positionMax.z); kv->SetFloat("AccnMinX", accelerationMin.x); kv->SetFloat("AccnMinY", accelerationMin.y); kv->SetFloat("AccnMinZ", accelerationMin.z); kv->SetFloat("AccnMaxX", accelerationMax.x); kv->SetFloat("AccnMaxY", accelerationMax.y); kv->SetFloat("AccnMaxZ", accelerationMax.z); kv->SetFloat("Gravity", fGravity); kv->SetFloat("CollisionDampening", m_fCollisionDampening); kv->SetFloat("GlowScale", m_fGlowScale); kv->SetFloat("GlowDeviation", m_fGlowDeviation); kv->SetFloat("BeamLength", m_fBeamLength); kv->SetBool("ScaleBeamByVelocity", m_bScaleBeamByVelocity); kv->SetBool("ScaleBeamByLifeLeft", m_bScaleBeamByLifeLeft); kv->SetInt("BeamPosition", m_iBeamPosition); kv->SetInt("Lighting", m_iLightingType); kv->SetFloat("LightApply", m_fLightApply); kv->SetFloat("DropletChance", m_fDropletChance); kv->SetFloat("ParticleLocal", m_fParticleLocal); kv->SetFloat("LifeLostOnCollision", m_fLifeLostOnCollision); kv->SetInt("ParticleSupply", m_iInitialParticleSupply); kv->SetInt("DrawType", m_DrawType); kv->SetString("CollisionTemplate", m_szCollisionTemplateName); kv->SetString("DropletTemplate", m_szDropletTemplateName); // save color nodes for (int i=0;i<5;i++) { char buf[64]; Q_snprintf(buf, 64, "Color%dUse", i); kv->SetInt(buf, m_Colors[i].bUse); Q_snprintf(buf, 64, "Color%dTime", i); kv->SetFloat(buf, m_Colors[i].fTime); Q_snprintf(buf, 64, "Color%dRed", i); kv->SetInt(buf, m_Colors[i].Color.r); Q_snprintf(buf, 64, "Color%dGreen", i); kv->SetInt(buf, m_Colors[i].Color.g); Q_snprintf(buf, 64, "Color%dBlue", i); kv->SetInt(buf, m_Colors[i].Color.b); } // save scale nodes for (int i=0;i<5;i++) { char buf[64]; Q_snprintf(buf, 64, "Scale%dUse", i); kv->SetInt(buf, m_Scales[i].bUse); Q_snprintf(buf, 64, "Scale%dTime", i); kv->SetFloat(buf, m_Scales[i].fTime); Q_snprintf(buf, 64, "Scale%dValue", i); kv->SetFloat(buf, m_Scales[i].fScale); } // save alpha nodes for (int i=0;i<5;i++) { char buf[64]; Q_snprintf(buf, 64, "Alpha%dUse", i); kv->SetInt(buf, m_Alphas[i].bUse); Q_snprintf(buf, 64, "Alpha%dTime", i); kv->SetFloat(buf, m_Alphas[i].fTime); Q_snprintf(buf, 64, "Alpha%dValue", i); kv->SetFloat(buf, m_Alphas[i].fAlpha); } // collision kv->SetInt("Collision", m_UseCollision); kv->SetString("CollisionSound", m_szCollisionSoundName); kv->SetString("CollisionDecal", m_szCollisionDecalName); // save the template kv->SaveToFile( filesystem, filename ); } KeyValues* CASWGenericEmitterCache::FindTemplate(const char* szTemplateName) { //Msg("CASWGenericEmitterCache::FindTemplate %s\n", szTemplateName); // check if it's in our cache of templates already int k = m_TemplateNames.Count(); for (int i=0;iLoadFromFile( filesystem, buf, "GAME" )) { Warning("Failed to load particle emitter: %s\n", szTemplateName); return NULL; } // add it to the cache m_Templates.AddToTail(kv); int length = Q_strlen(szTemplateName)+1; char *pNewString = new char[length]; Q_memcpy( pNewString, szTemplateName, length ); m_TemplateNames.AddToTail(pNewString); //Msg(" cache miss (now have %d templates loaded)\n", m_Templates.Count()); return kv; } CASWGenericEmitterCache::~CASWGenericEmitterCache() { // free up all the keyvalues we allocated int k=m_Templates.Count(); for (int i=0;ideleteThis(); // also free the template name strings we allocated delete[] m_TemplateNames[i]; } } void CASWGenericEmitterCache::ListCachedEmitters() { int k=m_Templates.Count(); for (int i=0;i