//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "cbase.h" #include "particlemgr.h" #include "particle_prototype.h" #include "particle_util.h" #include "surfinfo.h" #include "baseparticleentity.h" #include "materialsystem/imaterialsystemhardwareconfig.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" // ------------------------------------------------------------------------- // // Definitions // ------------------------------------------------------------------------- // #define NUM_AR2_EXPLOSION_PARTICLES 70 #define AR2_DUST_RADIUS 240 // 340 #define AR2_DUST_LIFETIME 4 #define AR2_DUST_LIFETIME_DELTA 6 #define AR2_DUST_SPEED 10000 #define AR2_DUST_STARTSIZE 8 #define AR2_DUST_ENDSIZE 32 #define AR2_DUST_ALPHA 0.5f #define AR2_DUST_FADE_IN_TIME 0.25f static Vector g_AR2DustColor1(0.35, 0.345, 0.33 ); static Vector g_AR2DustColor2(0.75, 0.75, 0.7); // ------------------------------------------------------------------------- // // Classes // ------------------------------------------------------------------------- // class C_AR2Explosion : public C_BaseParticleEntity, public IPrototypeAppEffect { public: DECLARE_CLASS( C_AR2Explosion, C_BaseParticleEntity ); DECLARE_CLIENTCLASS(); C_AR2Explosion(); ~C_AR2Explosion(); private: class AR2ExplosionParticle : public StandardParticle_t { public: float m_Dist; Vector m_Start; float m_Roll; float m_RollSpeed; float m_Dwell; }; // C_BaseEntity. public: virtual void OnDataChanged(DataUpdateType_t updateType); // IPrototypeAppEffect. public: virtual void Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs); // IParticleEffect. public: virtual void Update(float fTimeDelta); virtual void RenderParticles( CParticleRenderIterator *pIterator ); virtual void SimulateParticles( CParticleSimulateIterator *pIterator ); public: CParticleMgr *m_pParticleMgr; PMaterialHandle m_MaterialHandle; private: char m_szMaterialName[255]; C_AR2Explosion( const C_AR2Explosion & ); }; // Expose to the particle app. EXPOSE_PROTOTYPE_EFFECT(AR2Explosion, C_AR2Explosion); IMPLEMENT_CLIENTCLASS_DT(C_AR2Explosion, DT_AR2Explosion, AR2Explosion) RecvPropString( RECVINFO( m_szMaterialName ) ), END_RECV_TABLE() // ------------------------------------------------------------------------- // // Helpers. // ------------------------------------------------------------------------- // // Given a line segment from vStart to vEnd // and a list of convex polygons in pSurfInfos and nSurfInfos, // fill in the list of which polygons the segment intersects. // Returns the number of intersected surfaces. static int IntersectSegmentWithSurfInfos( const Vector &vStart, const Vector &vEnd, SurfInfo *pSurfInfos, const int nSurfInfos, SurfInfo ** pIntersections, Vector *pIntersectionPositions, int nMaxIntersections) { if(nMaxIntersections == 0) return 0; int nIntersections = 0; for(int i=0; i < nSurfInfos; i++) { SurfInfo *pSurf = &pSurfInfos[i]; // Does it intersect the plane? float dot1 = pSurf->m_Plane.DistTo(vStart); float dot2 = pSurf->m_Plane.DistTo(vEnd); if((dot1 > 0) != (dot2 > 0)) { float t = dot1 / (dot1 - dot2); Vector vIntersection = vStart + (vEnd - vStart) * t; // If the intersection is behind any edge plane, then it's not inside the polygon. unsigned long iEdge; for(iEdge=0; iEdge < pSurf->m_nVerts; iEdge++) { VPlane edgePlane; edgePlane.m_Normal = pSurf->m_Plane.m_Normal.Cross(pSurf->m_Verts[iEdge] - pSurf->m_Verts[(iEdge+1)%pSurf->m_nVerts]); VectorNormalize( edgePlane.m_Normal ); edgePlane.m_Dist = edgePlane.m_Normal.Dot(pSurf->m_Verts[iEdge]); if(edgePlane.DistTo(vIntersection) < 0.0f) break; } if(iEdge == pSurf->m_nVerts) { pIntersections[nIntersections] = pSurf; pIntersectionPositions[nIntersections] = vIntersection; ++nIntersections; if(nIntersections >= nMaxIntersections) break; } } } return nIntersections; } // ------------------------------------------------------------------------- // // C_AR2Explosion // ------------------------------------------------------------------------- // C_AR2Explosion::C_AR2Explosion() { m_pParticleMgr = NULL; m_MaterialHandle = INVALID_MATERIAL_HANDLE; } C_AR2Explosion::~C_AR2Explosion() { } void C_AR2Explosion::OnDataChanged(DataUpdateType_t updateType) { C_BaseEntity::OnDataChanged(updateType); if(updateType == DATA_UPDATE_CREATED) { Start(ParticleMgr(), NULL); } } static ConVar mat_reduceparticles( "mat_reduceparticles", "0" ); void C_AR2Explosion::Start(CParticleMgr *pParticleMgr, IPrototypeArgAccess *pArgs) { m_pParticleMgr = pParticleMgr; if(!pParticleMgr->AddEffect(&m_ParticleEffect, this)) return; if (!m_szMaterialName[0]) { Q_strncpy(m_szMaterialName, "particle/particle_noisesphere", sizeof( m_szMaterialName ) ); } m_MaterialHandle = m_ParticleEffect.FindOrAddMaterial(m_szMaterialName); // Precalculate stuff for the particle spawning.. #define NUM_DUSTEMITTER_SURFINFOS 128 SurfInfo surfInfos[NUM_DUSTEMITTER_SURFINFOS]; int nSurfInfos; // Center of explosion. Vector vCenter = GetAbsOrigin(); // HACKHACK.. when the engine bug is fixed, use origin. if ( IsXbox() ) { m_ParticleEffect.SetBBox( vCenter-Vector(300,300,300), vCenter+Vector(300,300,300) ); } #ifdef PARTICLEPROTOTYPE_APP float surfSize = 10000; nSurfInfos = 1; surfInfos[0].m_Verts[0].Init(-surfSize,-surfSize,0); surfInfos[0].m_Verts[1].Init(-surfSize,surfSize,0); surfInfos[0].m_Verts[2].Init(surfSize, surfSize,0); surfInfos[0].m_Verts[3].Init(surfSize,-surfSize,0); surfInfos[0].m_nVerts = 4; surfInfos[0].m_Plane.m_Normal.Init(0,0,1); surfInfos[0].m_Plane.m_Dist = -3; #else { nSurfInfos = 0; C_BaseEntity *ent = cl_entitylist->GetEnt( 0 ); if ( ent ) { nSurfInfos = engine->GetIntersectingSurfaces( ent->GetModel(), vCenter, AR2_DUST_RADIUS, true, surfInfos, NUM_DUSTEMITTER_SURFINFOS); } } #endif int nParticles = 0; int iParticlesToSpawn = NUM_AR2_EXPLOSION_PARTICLES; // In DX7, much fewer particles if ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 80 ) { iParticlesToSpawn *= 0.25; } else if ( mat_reduceparticles.GetBool() ) { iParticlesToSpawn *= 0.025; } if( nSurfInfos > 0 ) { // For nParticles*N, generate a ray and cast it out. If it hits anything, spawn a particle there. int nTestsPerParticle=3; for(int i=0; i < iParticlesToSpawn; i++) { for(int iTest=0; iTest < nTestsPerParticle; iTest++) { Vector randVec = RandomVector(-1,1); VectorNormalize( randVec ); Vector startPos = vCenter + randVec * AR2_DUST_RADIUS; randVec = RandomVector(-1,1); VectorNormalize( randVec ); Vector endPos = vCenter + randVec * AR2_DUST_RADIUS; #define MAX_SURFINFO_INTERSECTIONS 4 SurfInfo *pIntersected[MAX_SURFINFO_INTERSECTIONS]; Vector vIntersections[MAX_SURFINFO_INTERSECTIONS]; int nIntersections; nIntersections = IntersectSegmentWithSurfInfos( startPos, endPos, surfInfos, nSurfInfos, pIntersected, vIntersections, MAX_SURFINFO_INTERSECTIONS); if(nIntersections) { int iIntersection = rand() % nIntersections; Vector velocity; //velocity.Init(-1.0f + ((float)rand()/VALVE_RAND_MAX) * 2.0f, -1.0f + ((float)rand()/VALVE_RAND_MAX) * 2.0f, -1.0f + ((float)rand()/VALVE_RAND_MAX) * 2.0f); //velocity = velocity * FRand(m_MinSpeed, m_MaxSpeed); Vector direction = (vIntersections[iIntersection] - vCenter ); float dist = VectorNormalize( direction ); if(dist > AR2_DUST_RADIUS) dist = AR2_DUST_RADIUS; static float power = 2.0f; float falloffMul = pow(1.0f - dist / AR2_DUST_RADIUS, power); Vector reflection = direction - 2 * DotProduct( direction, pIntersected[iIntersection]->m_Plane.m_Normal ) * pIntersected[iIntersection]->m_Plane.m_Normal; VectorNormalize( reflection ); velocity = reflection * AR2_DUST_SPEED * falloffMul; // velocity = velocity + (vIntersections[iIntersection] - vCenter) * falloffMul; /* debugoverlay->AddLineOverlay( vIntersections[iIntersection], vIntersections[iIntersection] + reflection * 64, 128, 128, 255, false, 15.0 ); */ #if 1 AR2ExplosionParticle *pParticle = (AR2ExplosionParticle*)m_ParticleEffect.AddParticle( sizeof(AR2ExplosionParticle), m_MaterialHandle ); if(pParticle) { pParticle->m_Pos = vIntersections[iIntersection]; pParticle->m_Start = pParticle->m_Pos; pParticle->m_Dist = 8.0; pParticle->m_Velocity = velocity; // sound == 13031.496062992125984251968503937ips pParticle->m_Lifetime = -dist / 13031.5f - 0.1; pParticle->m_Roll = FRand( 0, M_PI * 2 ); pParticle->m_RollSpeed = FRand( -1, 1 ) * 0.4; pParticle->m_Dwell = AR2_DUST_LIFETIME + random->RandomFloat( 0, AR2_DUST_LIFETIME_DELTA ); nParticles++; break; } #endif } } } } // build interior smoke particles for(int i=nParticles; i < iParticlesToSpawn; i++) { Vector randVec = RandomVector(-1,1); VectorNormalize( randVec ); Vector endPos = vCenter + randVec * AR2_DUST_RADIUS / 4.0; Vector direction = (endPos - vCenter ); float dist = VectorNormalize( direction ) + random->RandomFloat( 0, AR2_DUST_RADIUS / 4.0 ); if(dist > AR2_DUST_RADIUS) dist = AR2_DUST_RADIUS; static float power = 2.0f; float falloffMul = pow(1.0f - dist / (AR2_DUST_RADIUS / 2), power); Vector velocity = direction * AR2_DUST_SPEED * falloffMul; AR2ExplosionParticle *pParticle = (AR2ExplosionParticle*)m_ParticleEffect.AddParticle( sizeof(AR2ExplosionParticle), m_MaterialHandle ); if(pParticle) { pParticle->m_Pos = endPos; pParticle->m_Start = pParticle->m_Pos; pParticle->m_Dist = 8.0; pParticle->m_Velocity = velocity; // sound == 13031.496062992125984251968503937ips pParticle->m_Lifetime = -dist / 13031.5f - 0.1; pParticle->m_Roll = FRand( 0, M_PI * 2 ); pParticle->m_RollSpeed = FRand( -1, 1 ) * 4.0; pParticle->m_Dwell = 0.5 * (AR2_DUST_LIFETIME + random->RandomFloat( 0, AR2_DUST_LIFETIME_DELTA )); } } } void C_AR2Explosion::Update(float fTimeDelta) { if(!m_pParticleMgr) return; } void C_AR2Explosion::SimulateParticles( CParticleSimulateIterator *pIterator ) { float dt = pIterator->GetTimeDelta(); AR2ExplosionParticle *pParticle = (AR2ExplosionParticle*)pIterator->GetFirst(); while ( pParticle ) { 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.... pIterator->RemoveParticle( pParticle ); } else { // Spin the thing pParticle->m_Roll += pParticle->m_RollSpeed * pIterator->GetTimeDelta(); // delayed? if ( pParticle->m_Lifetime >= 0.0f ) { // 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; } } pParticle = (AR2ExplosionParticle*)pIterator->GetNext(); } } 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 ); } }