// 02/08/02 November235: Particle System #include "hud.h" #include "cl_util.h" #include "const.h" #include #include "entity_state.h" #include "event_api.h" #include "cl_entity.h" #include "triangleapi.h" #include "com_model.h" #include "pmtrace.h" // for contents and traceline #include "pm_defs.h" #include "gl_local.h" #include "gl_studio.h" #include "gl_aurora.h" CParticleSystemManager g_pParticleSystems; // buz - static single object void UTIL_CreateAurora( cl_entity_t *ent, const char *file, int attachment, float lifetime ) { if( !g_fXashEngine || !g_fRenderInitialized ) return; int iCompare; // verify file exists // g-cont. idea! use COMPARE_FILE_TIME instead of LOAD_FILE if( COMPARE_FILE_TIME( file, file, &iCompare )) { CParticleSystem *pSystem = new CParticleSystem( ent, file, attachment, lifetime ); g_pParticleSystems.AddSystem( pSystem ); } else { ALERT( at_error, "CreateAurora: couldn't load %s\n", file ); } } void UTIL_RemoveAurora( cl_entity_t *ent ) { if( !g_fXashEngine || !g_fRenderInitialized ) return; CParticleSystem *pSystem = g_pParticleSystems.FindSystem( NULL, ent ); // find all the partsystems that attached with this entity while( pSystem != NULL ) { g_pParticleSystems.MarkSystemForDeletion( pSystem ); pSystem = g_pParticleSystems.FindSystem( pSystem, ent ); } } CParticleSystemManager :: CParticleSystemManager( void ) { m_pFirstSystem = NULL; } CParticleSystemManager :: ~CParticleSystemManager( void ) { ClearSystems(); } void CParticleSystemManager :: AddSystem( CParticleSystem *pNewSystem ) { pNewSystem->m_pNextSystem = m_pFirstSystem; m_pFirstSystem = pNewSystem; } CParticleSystem *CParticleSystemManager :: FindSystem( CParticleSystem *pFirstSystem, cl_entity_t *pEntity ) { CParticleSystem *pSys; if( pFirstSystem != NULL ) pSys = pFirstSystem->m_pNextSystem; else pSys = m_pFirstSystem; while( pSys != NULL ) { if( pEntity == pSys->m_pEntity ) return pSys; pSys = pSys->m_pNextSystem; } return NULL; } void CParticleSystemManager :: MarkSystemForDeletion( CParticleSystem *pSys ) { // parent entity is removed from server. pSys->MarkForDeletion(); pSys->m_pEntity = NULL; } // blended particles don't use the z-buffer, so we need to sort them before drawing. // for efficiency, only the systems are sorted - individual particles just get drawn in order of creation. // (this should actually make things look better - no ugly popping when one particle passes through another.) void CParticleSystemManager :: SortSystems( void ) { CParticleSystem *pSystem, *pLast; CParticleSystem *pBeforeCompare, *pCompare; if( !m_pFirstSystem ) return; // calculate how far away each system is from the viewer for( pSystem = m_pFirstSystem; pSystem; pSystem = pSystem->m_pNextSystem ) pSystem->CalculateDistance(); // do an insertion sort on the systems pLast = m_pFirstSystem; pSystem = pLast->m_pNextSystem; while( pSystem ) { if( pLast->m_fViewerDist < pSystem->m_fViewerDist ) { // pSystem is in the wrong place! First, let's unlink it from the list pLast->m_pNextSystem = pSystem->m_pNextSystem; // then find somewhere to insert it if( m_pFirstSystem == pLast || m_pFirstSystem->m_fViewerDist < pSystem->m_fViewerDist ) { // pSystem comes before the first system, insert it there pSystem->m_pNextSystem = m_pFirstSystem; m_pFirstSystem = pSystem; } else { // insert pSystem somewhere within the sorted part of the list pBeforeCompare = m_pFirstSystem; pCompare = pBeforeCompare->m_pNextSystem; while( pCompare != pLast ) { if( pCompare->m_fViewerDist < pSystem->m_fViewerDist ) { // pSystem comes before pCompare. We've found where it belongs. break; } pBeforeCompare = pCompare; pCompare = pBeforeCompare->m_pNextSystem; } // we've found where pSystem belongs. Insert it between pBeforeCompare and pCompare. pBeforeCompare->m_pNextSystem = pSystem; pSystem->m_pNextSystem = pCompare; } } else { // pSystem is in the right place, move on pLast = pSystem; } pSystem = pLast->m_pNextSystem; } } void CParticleSystemManager :: UpdateSystems( void ) { AURSTATE state; if( FBitSet( RI->params, ( RP_ENVVIEW|RP_SKYVIEW ))) return; CParticleSystem *pSystem; CParticleSystem *pLast = NULL; pSystem = m_pFirstSystem; while( pSystem ) { state = pSystem->UpdateSystem( tr.frametime ); if( state != AURORA_REMOVE ) { if( state == AURORA_DRAW ) pSystem->DrawSystem(); pLast = pSystem; pSystem = pSystem->m_pNextSystem; } else { // delete this system if( pLast ) { pLast->m_pNextSystem = pSystem->m_pNextSystem; delete pSystem; pSystem = pLast->m_pNextSystem; } else { // deleting the first system m_pFirstSystem = pSystem->m_pNextSystem; delete pSystem; pSystem = m_pFirstSystem; } } } gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); } void CParticleSystemManager :: ClearSystems( void ) { CParticleSystem *pSystem = m_pFirstSystem; CParticleSystem *pTemp; while( pSystem ) { pTemp = pSystem->m_pNextSystem; delete pSystem; pSystem = pTemp; } m_pFirstSystem = NULL; } CParticleType :: CParticleType( CParticleType *pNext ) { m_pSprayType = m_pOverlayType = NULL; m_StartAngle = RandomRange( 45.0f ); m_pNext = pNext; m_szName[0] = 0; m_hSprite = 0; m_StartRed = m_StartGreen = m_StartBlue = m_StartAlpha = RandomRange( 1.0f ); m_EndRed = m_EndGreen = m_EndBlue = m_EndAlpha = RandomRange( 1.0f ); m_iRenderMode = kRenderTransAdd; m_iDrawCond = CONTENTS_NONE; m_bIsDefined = false; m_bEndFrame = false; m_bBouncing = false; } CParticle* CParticleType :: CreateParticle( CParticleSystem *pSys ) { if( !pSys ) return NULL; CParticle *pPart = pSys->ActivateParticle(); if( !pPart ) return NULL; pPart->age = 0.0f; pPart->age_death = m_Life.GetInstance(); InitParticle( pPart, pSys ); return pPart; } void CParticleType :: InitParticle( CParticle *pPart, CParticleSystem *pSys ) { float fLifeRecip; if( pPart->age_death > 0.0f ) fLifeRecip = 1.0f / pPart->age_death; else fLifeRecip = 1.0f; pPart->pType = this; pPart->velocity = pPart->origin = g_vecZero; pPart->accel.x = pPart->accel.y = 0.0f; pPart->accel.z = m_Gravity.GetInstance(); pPart->m_pEntity = NULL; CParticle *pOverlay = NULL; if( m_pOverlayType ) { // create an overlay for this particle pOverlay = pSys->ActivateParticle(); if( pOverlay ) { pOverlay->age = pPart->age; pOverlay->age_death = pPart->age_death; m_pOverlayType->InitParticle( pOverlay, pSys ); } } pPart->m_pOverlay = pOverlay; if( m_pSprayType ) pPart->age_spray = 1.0f / m_SprayRate.GetInstance(); else pPart->age_spray = 0.0f; pPart->m_fSize = m_StartSize.GetInstance(); if( m_EndSize.IsDefined( )) pPart->m_fSizeStep = m_EndSize.GetOffset( pPart->m_fSize ) * fLifeRecip; else pPart->m_fSizeStep = m_SizeDelta.GetInstance(); pPart->frame = m_StartFrame.GetInstance(); if( m_EndFrame.IsDefined( )) pPart->m_fFrameStep = m_EndFrame.GetOffset( pPart->frame ) * fLifeRecip; else pPart->m_fFrameStep = m_FrameRate.GetInstance(); pPart->m_fAlpha = m_StartAlpha.GetInstance(); pPart->m_fAlphaStep = m_EndAlpha.GetOffset( pPart->m_fAlpha ) * fLifeRecip; pPart->m_fRed = m_StartRed.GetInstance(); pPart->m_fRedStep = m_EndRed.GetOffset( pPart->m_fRed ) * fLifeRecip; pPart->m_fGreen = m_StartGreen.GetInstance(); pPart->m_fGreenStep = m_EndGreen.GetOffset( pPart->m_fGreen ) * fLifeRecip; pPart->m_fBlue = m_StartBlue.GetInstance(); pPart->m_fBlueStep = m_EndBlue.GetOffset( pPart->m_fBlue ) * fLifeRecip; pPart->m_fAngle = m_StartAngle.GetInstance(); pPart->m_fAngleStep = m_AngleDelta.GetInstance(); pPart->m_fDrag = m_Drag.GetInstance(); float fWindStrength = m_WindStrength.GetInstance(); float fWindYaw = m_WindYaw.GetInstance(); if( fWindStrength != 0 ) { if( fWindYaw == 0 ) { // angle = 0, sin 0, cos 1 pPart->m_vecWind.x = 1.0f; pPart->m_vecWind.y = 0.0f; pPart->m_vecWind.z = 0.0f; } else { float fSinWindYaw = CParticleSystem :: CosLookup( fWindYaw ); float fCosWindYaw = CParticleSystem :: SinLookup( fWindYaw ); pPart->m_vecWind.x = fCosWindYaw; pPart->m_vecWind.y = fSinWindYaw; pPart->m_vecWind.z = 0; } // rotate wind vector into world space pPart->m_vecWind = pSys->entityMatrix.VectorRotate( pPart->m_vecWind ) * fWindStrength; } else { pPart->m_vecWind = g_vecZero; } } float CParticleSystem :: c_fCosTable[360 + 90]; bool CParticleSystem :: c_bCosTableInit = false; //============================================ CParticleSystem :: CParticleSystem( cl_entity_t *ent, const char *szFilename, int attachment, float lifetime ) { int iParticles = 100; // default m_iKillCondition = CONTENTS_NONE; m_iEntAttachment = attachment; m_pActiveParticle = NULL; m_pMainParticle = NULL; m_fLifeTime = lifetime; m_pNextSystem = NULL; m_iLightingModel = 0; m_fViewerDist = 0.0f; m_pFirstType = NULL; m_pEntity = ent; enable = true; entityMatrix.Identity(); if( !c_bCosTableInit ) { for( int i = 0; i < 360 + 90; i++ ) c_fCosTable[i] = cos( i * M_PI / 180.0f ); c_bCosTableInit = true; } char *afile = (char *)gEngfuncs.COM_LoadFile( (char *)szFilename, 5, NULL ); char szToken[1024]; char *pfile = afile; if( !afile ) { ALERT( at_error, "couldn't load %s.\n", szFilename ); return; } else { pfile = COM_ParseFile( pfile, szToken ); while( pfile ) { if( !Q_stricmp( szToken, "particles" )) { pfile = COM_ParseFile( pfile, szToken ); iParticles = Q_atoi( szToken ); } else if( !Q_stricmp( szToken, "maintype" )) { pfile = COM_ParseFile( pfile, szToken ); m_pMainType = AddPlaceholderType( szToken ); } else if( !Q_stricmp( szToken, "attachment" )) { pfile = COM_ParseFile( pfile, szToken ); m_iEntAttachment = Q_atoi( szToken ); } else if( !Q_stricmp( szToken, "lightmodel" )) { pfile = COM_ParseFile( pfile, szToken ); m_iLightingModel = Q_atoi( szToken ); } else if( !Q_stricmp( szToken, "lifetime" )) { pfile = COM_ParseFile( pfile, szToken ); m_fLifeTime = tr.time + Q_atof( szToken ); } else if( !Q_stricmp( szToken, "killcondition" )) { pfile = COM_ParseFile( pfile, szToken ); if( !Q_stricmp( szToken, "empty" )) { m_iKillCondition = CONTENTS_EMPTY; } else if( !Q_stricmp( szToken, "water" )) { m_iKillCondition = CONTENTS_WATER; } else if( !Q_stricmp( szToken, "solid" )) { m_iKillCondition = CONTENTS_SOLID; } } else if( !Q_stricmp( szToken, "{" )) { // parse new type this->ParseType( pfile ); } pfile = COM_ParseFile( pfile, szToken ); } } gEngfuncs.COM_FreeFile( afile ); AllocateParticles( iParticles ); } void CParticleSystem :: AllocateParticles( int iParticles ) { m_pAllParticles = new CParticle[iParticles]; memset( m_pAllParticles, 0, sizeof( CParticle ) * iParticles ); m_pFreeParticle = m_pAllParticles; m_pActiveParticle = NULL; m_pMainParticle = NULL; // initialise the linked list CParticle *pLast = m_pAllParticles; CParticle *pParticle = pLast + 1; for( int i = 1; i < iParticles; i++ ) { pLast->nextpart = pParticle; pLast = pParticle; pParticle++; } pLast->nextpart = NULL; } CParticleSystem :: ~CParticleSystem( void ) { delete[] m_pAllParticles; CParticleType *pType = m_pFirstType; CParticleType *pNext; while( pType ) { pNext = pType->m_pNext; delete pType; pType = pNext; } } // returns the CParticleType with the given name, if there is one CParticleType *CParticleSystem :: GetType( const char *szName ) { for( CParticleType *pType = m_pFirstType; pType != NULL; pType = pType->m_pNext ) { if( !Q_stricmp( pType->m_szName, szName )) return pType; } return NULL; } CParticleType *CParticleSystem :: AddPlaceholderType( const char *szName ) { m_pFirstType = new CParticleType( m_pFirstType ); Q_strncpy( m_pFirstType->m_szName, szName, sizeof( m_pFirstType->m_szName )); return m_pFirstType; } // creates a new particletype from the given file // NB: this changes the value of szFile. CParticleType *CParticleSystem :: ParseType( char *&pfile ) { CParticleType *pType = new CParticleType(); // parse the .aur file char szToken[1024]; pfile = COM_ParseFile( pfile, szToken ); while( Q_stricmp( szToken, "}" )) { if( !pfile ) break; if( !Q_stricmp( szToken, "name" )) { pfile = COM_ParseFile( pfile, szToken ); Q_strncpy( pType->m_szName, szToken, sizeof( pType->m_szName )); CParticleType *pTemp = GetType( szToken ); if( pTemp ) { // there's already a type with this name if( pTemp->m_bIsDefined ) ALERT( at_warning, "Particle type %s is defined more than once!\n", szToken ); // copy all our data into the existing type, throw away the type we were making *pTemp = *pType; delete pType; pType = pTemp; pType->m_bIsDefined = true; // record the fact that it's defined, so we won't need to add it to the list } } else if( !Q_stricmp( szToken, "gravity" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_Gravity = RandomRange( szToken ); } else if( !Q_stricmp( szToken, "windyaw" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_WindYaw = RandomRange( szToken ); } else if( !Q_stricmp( szToken, "windstrength" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_WindStrength = RandomRange( szToken ); } else if( !Q_stricmp( szToken, "sprite" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_hSprite = SPR_Load( szToken ); } else if( !Q_stricmp( szToken, "startalpha" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_StartAlpha = RandomRange( szToken ); } else if( !Q_stricmp( szToken, "endalpha" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_EndAlpha = RandomRange( szToken ); } else if( !Q_stricmp( szToken, "startred" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_StartRed = RandomRange( szToken ); } else if( !Q_stricmp( szToken, "endred" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_EndRed = RandomRange( szToken ); } else if( !Q_stricmp( szToken, "startgreen" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_StartGreen = RandomRange( szToken ); } else if( !Q_stricmp( szToken, "endgreen" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_EndGreen = RandomRange( szToken ); } else if( !Q_stricmp( szToken, "startblue" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_StartBlue = RandomRange( szToken ); } else if( !Q_stricmp( szToken, "endblue" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_EndBlue = RandomRange( szToken ); } else if( !Q_stricmp( szToken, "startsize" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_StartSize = RandomRange( szToken ); } else if( !Q_stricmp( szToken, "sizedelta" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_SizeDelta = RandomRange( szToken ); } else if( !Q_stricmp( szToken, "endsize" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_EndSize = RandomRange( szToken ); } else if( !Q_stricmp( szToken, "startangle" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_StartAngle = RandomRange( szToken ); } else if( !Q_stricmp( szToken, "angledelta" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_AngleDelta = RandomRange( szToken ); } else if( !Q_stricmp( szToken, "startframe" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_StartFrame = RandomRange( szToken ); } else if( !Q_stricmp( szToken, "endframe" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_EndFrame = RandomRange( szToken ); pType->m_bEndFrame = true; } else if( !Q_stricmp( szToken, "framerate" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_FrameRate = RandomRange( szToken ); } else if( !Q_stricmp( szToken, "lifetime" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_Life = RandomRange( szToken ); } else if( !Q_stricmp( szToken, "spraytype" )) { pfile = COM_ParseFile( pfile, szToken ); CParticleType *pTemp = GetType( szToken ); if( pTemp ) pType->m_pSprayType = pTemp; else pType->m_pSprayType = AddPlaceholderType( szToken ); } else if( !Q_stricmp( szToken, "overlaytype" )) { pfile = COM_ParseFile( pfile, szToken ); CParticleType *pTemp = GetType( szToken ); if( pTemp ) pType->m_pOverlayType = pTemp; else pType->m_pOverlayType = AddPlaceholderType( szToken ); } else if( !Q_stricmp( szToken, "sprayrate" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_SprayRate = RandomRange( szToken ); } else if( !Q_stricmp( szToken, "sprayforce" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_SprayForce = RandomRange( szToken ); } else if( !Q_stricmp( szToken, "spraypitch" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_SprayPitch = RandomRange( szToken ); } else if( !Q_stricmp( szToken, "sprayyaw" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_SprayYaw = RandomRange( szToken ); } else if( !Q_stricmp( szToken, "drag" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_Drag = RandomRange( szToken ); } else if( !Q_stricmp( szToken, "bounce" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_Bounce = RandomRange( szToken ); if( pType->m_Bounce.m_flMin != 0 || pType->m_Bounce.m_flMax != 0 ) pType->m_bBouncing = true; } else if( !Q_stricmp( szToken, "bouncefriction" )) { pfile = COM_ParseFile( pfile, szToken ); pType->m_BounceFriction = RandomRange( szToken ); } else if( !Q_stricmp( szToken, "rendermode" )) { pfile = COM_ParseFile( pfile, szToken ); if( !Q_stricmp( szToken, "additive" )) { pType->m_iRenderMode = kRenderTransAdd; } else if( !Q_stricmp( szToken, "solid" )) { pType->m_iRenderMode = kRenderTransAlpha; } else if( !Q_stricmp( szToken, "texture" )) { pType->m_iRenderMode = kRenderTransTexture; } else if( !Q_stricmp( szToken, "color" )) { pType->m_iRenderMode = kRenderTransColor; } } else if( !Q_stricmp( szToken, "drawcondition" )) { pfile = COM_ParseFile( pfile, szToken ); if( !Q_stricmp( szToken, "empty" )) { pType->m_iDrawCond = CONTENTS_EMPTY; } else if( !Q_stricmp( szToken, "water" )) { pType->m_iDrawCond = CONTENTS_WATER; } else if( !Q_stricmp( szToken, "solid" )) { pType->m_iDrawCond = CONTENTS_SOLID; } else if( !Q_stricmp( szToken, "special" ) || !Q_stricmp( szToken, "special1" )) { pType->m_iDrawCond = CONTENTS_SPECIAL1; } else if( !Q_stricmp( szToken, "special2" )) { pType->m_iDrawCond = CONTENTS_SPECIAL2; } else if( !Q_stricmp( szToken, "special3" )) { pType->m_iDrawCond = CONTENTS_SPECIAL3; } else if( !Q_stricmp( szToken, "spotlight" )) { pType->m_iDrawCond = CONTENTS_SPOTLIGHT; } } // get the next token pfile = COM_ParseFile( pfile, szToken ); } if( !pType->m_bIsDefined ) { // if this is a newly-defined type, we need to add it to the list pType->m_pNext = m_pFirstType; m_pFirstType = pType; pType->m_bIsDefined = true; } return pType; } CParticle *CParticleSystem :: ActivateParticle( void ) { CParticle *pActivated = m_pFreeParticle; if( pActivated ) { m_pFreeParticle = pActivated->nextpart; pActivated->nextpart = m_pActiveParticle; m_pActiveParticle = pActivated; } return pActivated; } void CParticleSystem :: MarkForDeletion( void ) { if( m_pMainParticle ) { m_pMainParticle->age_death = 0.0f; // die now m_pMainParticle = NULL; } // completely remove for time-based systems if( m_fLifeTime != 0.0f ) m_pEntity = NULL; } void CParticleSystem :: CalculateDistance( void ) { if( !m_pActiveParticle ) return; Vector offset = GetVieworg() - m_pActiveParticle->origin; // just pick one m_fViewerDist = DotProduct( offset, offset ); } AURSTATE CParticleSystem :: UpdateSystem( float frametime ) { if( m_pEntity != NULL ) { // don't update if the system is outside the player's PVS. if( m_pEntity->curstate.messagenum != r_currentMessageNum ) return AURORA_INVISIBLE; // time-based particle system if( m_fLifeTime != 0.0f ) { enable = (m_fLifeTime >= tr.time) ? true : false; } else { enable = (m_pEntity->curstate.body) ? true : false; } // check for contents to remove if( m_iKillCondition == POINT_CONTENTS( m_pEntity->origin )) { m_pEntity = NULL; enable = false; } } else { enable = false; } if( m_pEntity != NULL ) { Vector angles = m_pEntity->angles; // stupid quake bug if( m_pEntity == GET_VIEWMODEL( )) angles.x = -angles.x; // get the system entity matrix if( m_iEntAttachment && m_pEntity->model->type == mod_studio ) angles = R_StudioAttachmentAng( m_pEntity, m_iEntAttachment - 1 ); entityMatrix = matrix3x3( angles ); } if( m_pMainParticle == NULL ) { if( enable ) { CParticleType *pType = m_pMainType; if( pType ) { m_pMainParticle = pType->CreateParticle( this ); if( m_pMainParticle ) { // first origin initialize if( m_iEntAttachment && m_pEntity->model->type == mod_studio ) m_pMainParticle->origin = R_StudioAttachmentPos( m_pEntity, m_iEntAttachment - 1 ); else m_pMainParticle->origin = m_pEntity->origin; m_pMainParticle->m_pEntity = m_pEntity; m_pMainParticle->age_death = -1.0f; // never die } } } } else if( !enable ) { MarkForDeletion(); } // last particle is died, allow to remove partsystem if( !m_pEntity && !m_pActiveParticle ) return AURORA_REMOVE; CParticle *pParticle = m_pActiveParticle; CParticle *pLast = NULL; while( pParticle ) { if( UpdateParticle( pParticle, frametime )) { pLast = pParticle; pParticle = pParticle->nextpart; } else { // deactivate it if( pLast ) { pLast->nextpart = pParticle->nextpart; pParticle->nextpart = m_pFreeParticle; m_pFreeParticle = pParticle; pParticle = pLast->nextpart; } else { // deactivate the first CParticle in the list m_pActiveParticle = pParticle->nextpart; pParticle->nextpart = m_pFreeParticle; m_pFreeParticle = pParticle; pParticle = m_pActiveParticle; } } } return AURORA_DRAW; } void CParticleSystem :: DrawSystem( void ) { CParticle *pParticle = m_pActiveParticle; if( m_pEntity != NULL ) { // don't draw if the system is outside the player's PVS. if( m_pEntity->curstate.messagenum != r_currentMessageNum ) return; } m_fHasProjectionLighting = HasDynamicLights(); for( pParticle = m_pActiveParticle; pParticle; pParticle = pParticle->nextpart ) { if( pParticle->m_fSize <= 0 || !ParticleIsVisible( pParticle )) continue; DrawParticle( pParticle, GetVLeft(), GetVUp( )); } } bool CParticleSystem :: ParticleIsVisible( CParticle *part ) { if( R_CullSphere( part->origin, part->m_fSize + 1 )) return false; return true; } bool CParticleSystem :: UpdateParticle( CParticle *part, float frametime ) { if( frametime == 0 ) return true; part->age += frametime; // is this particle bound to an entity? if( part->m_pEntity ) { if( enable ) { if( m_iEntAttachment && m_pEntity->model->type == mod_studio ) { float flSpeed = (R_StudioAttachmentPos( m_pEntity, m_iEntAttachment - 1 ) - part->origin).Length() * frametime; part->velocity = entityMatrix.GetForward() * flSpeed; part->origin = R_StudioAttachmentPos( m_pEntity, m_iEntAttachment - 1 ); } else { float flSpeed = (m_pEntity->origin - part->origin).Length() * frametime; part->velocity = entityMatrix.GetForward() * flSpeed; part->origin = m_pEntity->origin; } } else { // entity is switched off, die return false; } } else { // not tied to an entity, check whether it's time to die if( part->age_death >= 0.0f && part->age > part->age_death ) return false; // apply acceleration and velocity if( part->m_fDrag ) part->velocity += (part->velocity - part->m_vecWind) * (-part->m_fDrag * frametime); part->velocity += part->accel * frametime; part->origin += part->velocity * frametime; if( part->pType->m_bBouncing ) { Vector vecTarget = part->origin + part->velocity * frametime; pmtrace_t *tr = gEngfuncs.PM_TraceLine( part->origin, vecTarget, PM_TRACELINE_PHYSENTSONLY, 2, -1 ); if( tr->fraction < 1.0f ) { part->origin = tr->endpos; float bounceforce = DotProduct( tr->plane.normal, part->velocity ); float newspeed = (1.0f - part->pType->m_BounceFriction.GetInstance( )); part->velocity = part->velocity * newspeed; part->velocity += tr->plane.normal * (-bounceforce * (newspeed + part->pType->m_Bounce.GetInstance())); } } } // spray children if( part->age_spray && part->age > part->age_spray ) { part->age_spray = part->age + 1.0f / part->pType->m_SprayRate.GetInstance(); if( part->pType->m_pSprayType ) { CParticle *pChild = part->pType->m_pSprayType->CreateParticle( this ); if( pChild ) { pChild->origin = part->origin; pChild->velocity = part->velocity; float fSprayForce = part->pType->m_SprayForce.GetInstance(); if( fSprayForce ) { Vector vecLocalAngles, vecSprayDir; vecLocalAngles.x = part->pType->m_SprayPitch.GetInstance(); vecLocalAngles.y = part->pType->m_SprayYaw.GetInstance(); vecLocalAngles.z = 0.0f; // setup particle local direction if( vecLocalAngles != g_vecZero ) AngleVectors( vecLocalAngles, vecSprayDir, NULL, NULL ); else vecSprayDir = Vector( 1, 0, 0 ); // fast case pChild->velocity += entityMatrix.VectorRotate( vecSprayDir ) * fSprayForce; } } } } part->m_fSize += part->m_fSizeStep * frametime; part->m_fAlpha += part->m_fAlphaStep * frametime; part->m_fRed += part->m_fRedStep * frametime; part->m_fGreen += part->m_fGreenStep * frametime; part->m_fBlue += part->m_fBlueStep * frametime; part->frame += part->m_fFrameStep * frametime; if( part->m_fAngleStep ) { part->m_fAngle += part->m_fAngleStep * frametime; while( part->m_fAngle < 0 ) part->m_fAngle += 360; while( part->m_fAngle > 360 ) part->m_fAngle -= 360; } return true; } void CParticleSystem :: DrawParticle( CParticle *part, vec3_t &right, vec3_t &up ) { float fSize = part->m_fSize; Vector point[4]; Vector origin = part->origin; Vector absmin, absmax; Vector4D partColor; float fCosSize = CosLookup( part->m_fAngle ) * fSize; float fSinSize = SinLookup( part->m_fAngle ) * fSize; // calculate the four corners of the sprite point[0] = origin + up * -fCosSize + right * -fSinSize; point[1] = origin + up * fSinSize + right * -fCosSize; point[2] = origin + up * fCosSize + right * fSinSize; point[3] = origin + up * -fSinSize + right * fCosSize; ClearBounds( absmin, absmax ); for( int i = 0; i < 4; i++ ) AddPointToBounds( point[i], absmin, absmax ); int iContents = CONTENTS_NONE; model_t *pModel; for( CParticle *pDraw = part; pDraw; pDraw = pDraw->m_pOverlay ) { if( !pDraw->pType->m_hSprite ) continue; if( pDraw->pType->m_iDrawCond ) { if( pDraw->pType->m_iDrawCond == CONTENTS_SPOTLIGHT ) { if( !m_fHasProjectionLighting ) continue; // fast reject for( int i = 0; i < MAX_DLIGHTS; i++ ) { CDynLight *pl = &tr.dlights[i]; if( !pl->Active( )) continue; if( !pl->frustum.CullSphere( part->origin, part->m_fSize + 1 )) break; // cone intersected with particle } if( i == MAX_DLIGHTS ) continue; // no intersection } else { if( iContents == CONTENTS_NONE ) iContents = POINT_CONTENTS( origin ); if( iContents != pDraw->pType->m_iDrawCond ) continue; } } pModel = (model_t *)gEngfuncs.GetSpritePointer( pDraw->pType->m_hSprite ); // if we've reached the end of the sprite's frames, loop back while( pDraw->frame > pModel->numframes ) pDraw->frame -= pModel->numframes; while( pDraw->frame < 0 ) pDraw->frame += pModel->numframes; if( m_iLightingModel >= 1 ) { Vector lightColor; gEngfuncs.pTriAPI->LightAtPoint( part->origin, (float *)&lightColor ); lightColor *= (1.0f / 255.0f); partColor.x = pDraw->m_fRed * lightColor.x; partColor.y = pDraw->m_fGreen * lightColor.y; partColor.z = pDraw->m_fBlue * lightColor.z; partColor.w = pDraw->m_fAlpha; } else { partColor = Vector4D( pDraw->m_fRed, pDraw->m_fGreen, pDraw->m_fBlue, pDraw->m_fAlpha ); } #if 0 if( !gEngfuncs.pTriAPI->SpriteTexture( pModel, (int)pDraw->frame )) continue; gEngfuncs.pTriAPI->RenderMode( pDraw->pType->m_iRenderMode ); pglColor4f( partColor.x, partColor.y, partColor.z, partColor.w ); pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); pglBegin( GL_QUADS ); pglTexCoord2f( 0.0f, 1.0f ); pglVertex3fv( point[0] ); pglTexCoord2f( 0.0f, 0.0f ); pglVertex3fv( point[1] ); pglTexCoord2f( 1.0f, 0.0f ); pglVertex3fv( point[2] ); pglTexCoord2f( 1.0f, 1.0f ); pglVertex3fv( point[3] ); pglEnd(); #else int hTexture; if(( hTexture = R_GetSpriteTexture( pModel, pDraw->frame )) == 0 ) continue; CTransEntry entry; entry.SetRenderPrimitive( point, partColor, hTexture, pDraw->pType->m_iRenderMode ); entry.ComputeViewDistance( absmin, absmax ); RI->frame.trans_list.AddToTail( entry ); #endif } }