Paranoia2_original/cl_dll/render/gl_aurora.cpp

1170 lines
30 KiB
C++

// 02/08/02 November235: Particle System
#include "hud.h"
#include "cl_util.h"
#include "const.h"
#include <stringlib.h>
#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
}
}