/*** * * Copyright (C) 2002 The Wastes Project, All Rights Reserved. * * This product contains software technology from Valve Software, LLC, * Copyright © 1996-2001, Valve LLC, All rights reserved. * * Use, distribution, and modification of this source code and/or resulting * object code is restricted to non-commercial enhancements to products from * The Wastes Project. All other use, distribution, or modification is prohibited * without written permission from The Wastes Project. * ***/ #include #include "hud.h" #include "wrect.h" #include "cl_util.h" #include "tri.h" #include "pm_defs.h" #include "event_api.h" #include "ParticleBase.h" CParticleSystemManager g_ParticleSystemManager; void HUD_GetLastOrg( float *org ); //================================================== // CSpriteEmitter //================================================== CSpriteEmitter::CSpriteEmitter() { memset( m_szName, 0, sizeof( m_szName ) ); memset( m_Origin, 0, sizeof( m_Origin ) ); memset( m_Acceleration, 0, sizeof( m_Acceleration ) ); memset( m_StartOriginOffset, 0, sizeof( m_StartOriginOffset ) ); memset( m_StartOriginRange, 0, sizeof( m_StartOriginRange ) ); memset( m_StartVelocity, 0, sizeof( m_StartVelocity ) ); memset( m_MinVelocity, 0, sizeof( m_MinVelocity ) ); memset( m_MaxVelocity, 0, sizeof( m_MaxVelocity ) ); memset( m_flParticleLifetime, 0, sizeof( m_flParticleLifetime ) ); memset( m_flStartScaleRange, 0, sizeof( m_flStartScaleRange ) ); memset( m_flGrowScaleRange, 0, sizeof( m_flGrowScaleRange ) ); m_iNumSprites = 0; memset( m_iSpriteHandles, 0, sizeof( m_iSpriteHandles ) ); m_iMaxParticles = 1; m_flParticlesPerSecond = 0.0f; m_iRenderMode = PRM_Normal; m_iCoordSystem = PCS_Standard; m_flInactiveTime = 0.0f; m_flLastParticleTime = 0.0f; m_pParticles = 0; m_iNumParticles = 0; m_flInactiveStart = -1.0f; } CSpriteEmitter::CSpriteEmitter( const CSpriteEmitter &Cpy ) { strcpy( m_szName, Cpy.m_szName ); VectorCopy( Cpy.m_Origin, m_Origin ); VectorCopy( Cpy.m_Acceleration, m_Acceleration ); VectorCopy( Cpy.m_StartOriginOffset, m_StartOriginOffset ); VectorCopy( Cpy.m_StartOriginRange[0], m_StartOriginRange[0] ); VectorCopy( Cpy.m_StartOriginRange[1], m_StartOriginRange[1] ); VectorCopy( Cpy.m_StartVelocity[0], m_StartVelocity[0] ); VectorCopy( Cpy.m_StartVelocity[1], m_StartVelocity[1] ); VectorCopy( Cpy.m_MinVelocity, m_MinVelocity ); VectorCopy( Cpy.m_MaxVelocity, m_MaxVelocity ); memcpy( m_flParticleLifetime, Cpy.m_flParticleLifetime, sizeof( m_flParticleLifetime ) ); memcpy( m_flStartScaleRange, Cpy.m_flStartScaleRange, sizeof( m_flStartScaleRange ) ); memcpy( m_flGrowScaleRange, Cpy.m_flGrowScaleRange, sizeof( m_flGrowScaleRange ) ); m_iNumSprites = Cpy.m_iNumSprites; memcpy( m_iSpriteHandles, Cpy.m_iSpriteHandles, sizeof( m_iSpriteHandles ) ); m_iMaxParticles = Cpy.m_iMaxParticles; m_flParticlesPerSecond = Cpy.m_flParticlesPerSecond; m_iRenderMode = Cpy.m_iRenderMode; m_iCoordSystem = Cpy.m_iCoordSystem; m_flInactiveTime = Cpy.m_flInactiveTime; // These are unique to each emitter m_flLastParticleTime = 0.0f; m_pParticles = 0; m_iNumParticles = 0; m_flInactiveStart = -1.0f; } CSpriteEmitter::~CSpriteEmitter() { ClearList( m_pParticles ); } void CSpriteEmitter::Render() { if( m_flInactiveTime > 0.0f ) { psvec3_t origin, angles, forward, right, up; int idx = gEngfuncs.GetLocalPlayer()->index; pmtrace_t tr; // See if we shouldn't render the emitter gEngfuncs.GetViewAngles((float*)&angles); HUD_GetLastOrg((float*)&origin); AngleVectors(angles,forward,right,up); gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, false ); gEngfuncs.pEventAPI->EV_PushPMStates(); gEngfuncs.pEventAPI->EV_SetSolidPlayers( idx - 1 ); gEngfuncs.pEventAPI->EV_SetTraceHull( 1 ); gEngfuncs.pEventAPI->EV_PlayerTrace( origin, m_Origin, PM_WORLD_ONLY|PM_GLASS_IGNORE, -1, &tr ); gEngfuncs.pEventAPI->EV_PopPMStates(); if( tr.fraction != 1.0f ) { if( m_flInactiveStart == -1.0f ) m_flInactiveStart = gHUD.m_flTime; else if( gHUD.m_flTime >= m_flInactiveStart + m_flInactiveTime ) return; } else m_flInactiveStart = -1.0f; } CParticle *pCurPcl = m_pParticles; while( pCurPcl ) { const struct model_s *spritemodel; psvec3_t angles,forward,right,up; psvec3_t origin; spritemodel = gEngfuncs.GetSpritePointer( m_iSpriteHandles[ pCurPcl->m_iModelIndex ] ); gEngfuncs.GetViewAngles( angles ); AngleVectors( angles, forward, right, up ); if( m_iCoordSystem == PCS_Relative ) { VectorAdd( m_Origin, pCurPcl->m_Origin, origin ); } else { VectorCopy( pCurPcl->m_Origin, origin ); } R_Billboard( spritemodel, 0, m_iRenderMode, 1.0f, 1.0f, 1.0f, 1.0f, origin, up, right, pCurPcl->m_flScale ); pCurPcl = pCurPcl->m_pNext; } if( gHUD.m_flTime - m_flLastParticleTime >= 1/m_flParticlesPerSecond ) { m_flLastParticleTime = gHUD.m_flTime; AddParticle(); } } void CSpriteEmitter::Update( double flFrametime ) { // Dont update if we dont have to // if( m_flInactiveStart != -1.0f && gHUD.m_flTime >= m_flInactiveStart + m_flInactiveTime ) // return; CParticle *pCurPcl = m_pParticles; while( pCurPcl ) { VectorMA( pCurPcl->m_Origin, flFrametime, pCurPcl->m_Velocity, pCurPcl->m_Origin ); VectorAdd( pCurPcl->m_Velocity, m_Acceleration, pCurPcl->m_Velocity ); for( int i = 0;i < 3;i++ ) { if( m_MinVelocity[i] != 0 && pCurPcl->m_Velocity[i] < m_MinVelocity[i]) pCurPcl->m_Velocity[i] = m_MinVelocity[i]; else if( m_MaxVelocity[i] != 0 && pCurPcl->m_Velocity[i] > m_MaxVelocity[i] ) pCurPcl->m_Velocity[i] = m_MaxVelocity[i]; } pCurPcl->m_flScale += flFrametime * pCurPcl->m_flGrowScale; pCurPcl = pCurPcl->m_pNext; } // See if we should delete any particles // TODO: Clean this shit up will ya ? :) pCurPcl = m_pParticles; while( pCurPcl != NULL ) { if( pCurPcl->m_flLifetime - gHUD.m_flTime <= 0.0f ) { CParticle *pPrevPcl = m_pParticles; if( pPrevPcl == pCurPcl ) { m_pParticles = pCurPcl->m_pNext; delete pCurPcl; pCurPcl = m_pParticles; } else { while( pPrevPcl->m_pNext != NULL ) { if( pPrevPcl->m_pNext == pCurPcl ) { pPrevPcl->m_pNext = pCurPcl->m_pNext; delete pCurPcl; m_iNumParticles--; break; } pPrevPcl = pPrevPcl->m_pNext; } pCurPcl = pPrevPcl->m_pNext; } } else pCurPcl = pCurPcl->m_pNext; } } void CSpriteEmitter::AddParticle() { // Delete oldest particle if we are at max if( m_iNumParticles >= m_iMaxParticles && m_pParticles ) { CParticle *pDel = m_pParticles; m_pParticles = m_pParticles->m_pNext; delete pDel; } CParticle *pNewPcl = new CParticle; // Origin psvec3_t RangedOrigin; RangedOrigin[0] = gEngfuncs.pfnRandomFloat( m_StartOriginRange[0][0], m_StartOriginRange[1][0] ); RangedOrigin[1] = gEngfuncs.pfnRandomFloat( m_StartOriginRange[0][1], m_StartOriginRange[1][1] ); RangedOrigin[2] = gEngfuncs.pfnRandomFloat( m_StartOriginRange[0][2], m_StartOriginRange[1][2] ); if( m_iCoordSystem == PCS_Relative ) { VectorCopy( RangedOrigin, pNewPcl->m_Origin ); } else { VectorAdd( m_Origin, RangedOrigin, pNewPcl->m_Origin ); } VectorAdd( pNewPcl->m_Origin, m_StartOriginOffset, pNewPcl->m_Origin ); // Velocity VectorSet( pNewPcl->m_Velocity, gEngfuncs.pfnRandomFloat( m_StartVelocity[0][0], m_StartVelocity[1][0] ), gEngfuncs.pfnRandomFloat( m_StartVelocity[0][1], m_StartVelocity[1][1] ), gEngfuncs.pfnRandomFloat( m_StartVelocity[0][2], m_StartVelocity[1][2] ) ); pNewPcl->m_flLifetime = gHUD.m_flTime + gEngfuncs.pfnRandomFloat( m_flParticleLifetime[0], m_flParticleLifetime[1] ); pNewPcl->m_flScale = gEngfuncs.pfnRandomFloat( m_flStartScaleRange[0], m_flStartScaleRange[1] ); pNewPcl->m_flGrowScale = gEngfuncs.pfnRandomFloat( m_flGrowScaleRange[0], m_flGrowScaleRange[1] ); pNewPcl->m_iModelIndex = gEngfuncs.pfnRandomLong( 0, m_iNumSprites-1 ); if( m_pParticles ) m_pParticles->AddParticle( pNewPcl ); else m_pParticles = pNewPcl; m_iNumParticles++; } void CSpriteEmitter::ClearList( CParticle *pList ) { CParticle *pCurPcl = pList; while( pCurPcl ) { CParticle *pNextPcl = pCurPcl->m_pNext; delete pCurPcl; pCurPcl = pNextPcl; } } //================================================== // CParticleSystem //================================================== CParticleSystem::CParticleSystem() { memset( m_szName, 0, sizeof( m_szName ) ); memset( m_Origin, 0, sizeof( m_Origin ) ); } CParticleSystem::CParticleSystem( const CParticleSystem &Cpy ) { strcpy( m_szName, Cpy.m_szName ); VectorCopy( Cpy.m_Origin, m_Origin ); } CParticleSystem::~CParticleSystem() { } void CParticleSystem::Render( int Transparent ) { if( m_Emitters.size() ) { list::iterator iter; for( iter = m_Emitters.begin(); iter != m_Emitters.end(); iter++ ) { if( Transparent && iter->IsTransparent() ) iter->Render(); else if( !Transparent && !iter->IsTransparent() ) iter->Render(); } } } void CParticleSystem::Update( double flFrametime ) { if( m_Emitters.size() ) { list::iterator iter; for( iter = m_Emitters.begin(); iter != m_Emitters.end(); iter++ ) { VectorCopy( m_Origin, iter->m_Origin ); iter->Update( flFrametime ); } } } //================================================== // CParticleSystemManager //================================================== CParticleSystemManager::CParticleSystemManager() { memset( m_szCurrentLevel, 0, sizeof( m_szCurrentLevel ) ); } CParticleSystemManager::~CParticleSystemManager() { } void CParticleSystemManager::RenderNormal() { if( m_ActiveSystems.size() ) { list::iterator iter; for( iter = m_ActiveSystems.begin(); iter != m_ActiveSystems.end(); iter++ ) iter->Render( 0 ); } } void CParticleSystemManager::RenderTransparent() { if( m_ActiveSystems.size() ) { list::iterator iter; for( iter = m_ActiveSystems.begin(); iter != m_ActiveSystems.end(); iter++ ) iter->Render( 1 ); } } void CParticleSystemManager::Update( double flFrametime ) { if( m_ActiveSystems.size() ) { list::iterator iter; for( iter = m_ActiveSystems.begin(); iter != m_ActiveSystems.end(); iter++ ) iter->Update( flFrametime ); } } void CParticleSystemManager::AddParticleSystem( char *pszName, psvec3_t Origin ) { gEngfuncs.Con_Printf( "Adding particle system \"%s\" at %f %f %f\n", pszName, Origin[0], Origin[1], Origin[2] ); const CParticleSystem *pNewPclSystem = GetParticleSystem( pszName ); if( pNewPclSystem != NULL ) { m_ActiveSystems.push_back( *pNewPclSystem ); return; } gEngfuncs.Con_Printf( "Particle system \"%s\" not found!\n", pszName ); } const CParticleSystem *CParticleSystemManager::GetParticleSystem( char *pszName ) { if( m_ReferenceSystems.size() ) { list::iterator iter; for( iter = m_ReferenceSystems.begin(); iter != m_ReferenceSystems.end(); iter++ ) { if( stricmp( iter->m_szName, pszName ) == 0 ) return &*iter; } } return NULL; } const CSpriteEmitter *CParticleSystemManager::GetEmitter( char *pszName ) { if( m_ReferenceEmitters.size() ) { list::iterator iter; for( iter = m_ReferenceEmitters.begin(); iter != m_ReferenceEmitters.end(); iter++ ) { if( stricmp( iter->m_szName, pszName ) == 0 ) return &*iter; } } return NULL; } void CParticleSystemManager::CheckMap() { // Maps dont match, time to reinitialize emitters! if( stricmp( m_szCurrentLevel, gEngfuncs.pfnGetLevelName() ) != 0 ) { strcpy( m_szCurrentLevel, gEngfuncs.pfnGetLevelName() ); m_ActiveSystems.clear(); m_ReferenceEmitters.clear(); m_ReferenceSystems.clear(); // Test values #if 1 CSpriteEmitter newEmitter; strcpy( newEmitter.m_szName, "test.sewer_ventsmoke_emitter" ); VectorSet( newEmitter.m_StartVelocity[0], 50, 0, -50.0f ); VectorSet( newEmitter.m_StartVelocity[1], 100, 0, -100.0f ); newEmitter.m_flParticlesPerSecond = 10.0f; newEmitter.m_flParticleLifetime[0] = 5.0f; newEmitter.m_flParticleLifetime[1] = 6.0f; VectorSet( newEmitter.m_StartOriginOffset, 0, 0, 0 ); VectorSet( newEmitter.m_StartOriginRange[0], -10, -10, -10 ); VectorSet( newEmitter.m_StartOriginRange[1], 10, 10, 10 ); newEmitter.m_flStartScaleRange[0] = 10.0f; newEmitter.m_flStartScaleRange[1] = 15.0f; newEmitter.m_flGrowScaleRange[0] = 1.0f; newEmitter.m_flGrowScaleRange[1] = 2.0f; newEmitter.m_iRenderMode = PRM_TransAdd; newEmitter.m_iCoordSystem = PCS_Standard; newEmitter.m_iMaxParticles = 256; newEmitter.m_iNumSprites = 1; newEmitter.m_iSpriteHandles[0] = gEngfuncs.pfnSPR_Load( "sprites/circle.spr" ); newEmitter.m_flInactiveTime = 0.5f; m_ReferenceEmitters.push_back( newEmitter ); CParticleSystem newSystem; strcpy( newSystem.m_szName, "test.sewer_ventsmoke" ); newSystem.m_Emitters.push_back( newEmitter ); m_ReferenceSystems.push_back( newSystem ); // test.sewer_lightsmoke // test.sewer_ventsmoke /* CSpriteEmitter newEmitter; strcpy( newEmitter.m_szName, "test.testemitter" ); VectorSet( newEmitter.m_Origin, 0, 0, 50.0f ); newEmitter.m_Origin[2] -= 25.0f; VectorSet( newEmitter.m_StartVelocity[0], 0, 0, -500.0f ); VectorSet( newEmitter.m_StartVelocity[1], 0, 0, -750.0f ); newEmitter.m_flParticlesPerSecond = 100.0f; newEmitter.m_flParticleLifetime[0] = 4.0f; newEmitter.m_flParticleLifetime[1] = 5.0f; VectorSet( newEmitter.m_StartOriginOffset, 0, 0, 480 ); VectorSet( newEmitter.m_StartOriginRange[0], -500.0f, -500.0f, 0 ); VectorSet( newEmitter.m_StartOriginRange[1], 500.0f, 500.0f, 0 ); VectorSet( newEmitter.m_MinVelocity, 0, 0, 0 ); VectorSet( newEmitter.m_MaxVelocity, 0, 0, 0 ); newEmitter.m_flStartScaleRange[0] = 10.0; newEmitter.m_flStartScaleRange[1] = 15.0; newEmitter.m_flGrowScaleRange[0] = 0.0; newEmitter.m_flGrowScaleRange[1] = 0.0; newEmitter.m_iRenderMode = PRM_TransAdd; newEmitter.m_iCoordSystem = PCS_Standard; newEmitter.m_iMaxParticles = 1024; newEmitter.m_iNumSprites = 1; newEmitter.m_iSpriteHandles[0] = gEngfuncs.pfnSPR_Load( "sprites/circle.spr" ); newEmitter.m_flInactiveTime = 1.0f; m_ReferenceEmitters.push_back( newEmitter ); CParticleSystem newSystem; strcpy( newSystem.m_szName, "test.testsystem" ); VectorCopy( newEmitter.m_Origin, newSystem.m_Origin ); // newSystem.m_pEmitters.push_back( new CSpriteEmitter( g_ParticleSystemManager.GetEmitter( "test.testemitter" ) ) ); m_ActiveSystems.push_back( newSystem ); m_ReferenceSystems.push_back( newSystem );*/ #endif } }