1181 lines
31 KiB
C++
1181 lines
31 KiB
C++
/*
|
|
gl_rpart.cpp - quake-like particle system
|
|
Copyright (C) 2014 Uncle Mike
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
*/
|
|
|
|
#include "hud.h"
|
|
#include "cl_util.h"
|
|
#include "const.h"
|
|
#include <mathlib.h>
|
|
#include "gl_local.h"
|
|
#include "com_model.h"
|
|
#include "r_studioint.h"
|
|
#include "gl_rpart.h"
|
|
#include "pm_defs.h"
|
|
#include "event_api.h"
|
|
#include "triangleapi.h"
|
|
#include "gl_sprite.h"
|
|
|
|
CQuakePartSystem g_pParticles;
|
|
|
|
bool CQuakePart :: Evaluate( float gravity )
|
|
{
|
|
Vector org, org2, org3, vel;
|
|
|
|
float time = ( tr.time - m_flTime );
|
|
float time2 = time * time;
|
|
|
|
float curAlpha = m_flAlpha + m_flAlphaVelocity * time;
|
|
float curRadius = m_flRadius + m_flRadiusVelocity * time;
|
|
float curLength = m_flLength + m_flLengthVelocity * time;
|
|
|
|
if( curAlpha <= 0.0f || curRadius <= 0.0f || curLength <= 0.0f )
|
|
{
|
|
// faded out
|
|
return false;
|
|
}
|
|
|
|
Vector curColor = m_vecColor + m_vecColorVelocity * time;
|
|
|
|
org.x = m_vecOrigin.x + m_vecVelocity.x * time + m_vecAccel.x * time2;
|
|
org.y = m_vecOrigin.y + m_vecVelocity.y * time + m_vecAccel.y * time2;
|
|
org.z = m_vecOrigin.z + m_vecVelocity.z * time + m_vecAccel.z * time2 * gravity;
|
|
|
|
if( FBitSet( m_iFlags, FPART_UNDERWATER ))
|
|
{
|
|
// underwater particle
|
|
org2 = Vector( org.x, org.y, org.z + curRadius );
|
|
|
|
int contents = POINT_CONTENTS( org );
|
|
|
|
if( contents != CONTENTS_WATER && contents != CONTENTS_SLIME && contents != CONTENTS_LAVA )
|
|
{
|
|
// not underwater
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if( FBitSet( m_iFlags, FPART_FRICTION ))
|
|
{
|
|
// water friction affected particle
|
|
int contents = POINT_CONTENTS( org );
|
|
|
|
if( contents <= CONTENTS_WATER && contents >= CONTENTS_LAVA )
|
|
{
|
|
// add friction
|
|
switch( contents )
|
|
{
|
|
case CONTENTS_WATER:
|
|
m_vecVelocity *= 0.25f;
|
|
m_vecAccel *= 0.25f;
|
|
break;
|
|
case CONTENTS_SLIME:
|
|
m_vecVelocity *= 0.20f;
|
|
m_vecAccel *= 0.20f;
|
|
break;
|
|
case CONTENTS_LAVA:
|
|
m_vecVelocity *= 0.10f;
|
|
m_vecAccel *= 0.10f;
|
|
break;
|
|
}
|
|
|
|
// don't add friction again
|
|
m_iFlags &= ~FPART_FRICTION;
|
|
curLength = 1.0f;
|
|
|
|
// reset
|
|
m_flTime = tr.time;
|
|
m_vecColor = curColor;
|
|
m_flAlpha = curAlpha;
|
|
m_flRadius = curRadius;
|
|
m_vecOrigin = org;
|
|
|
|
// don't stretch
|
|
m_iFlags &= ~FPART_STRETCH;
|
|
m_flLengthVelocity = 0.0f;
|
|
m_flLength = curLength;
|
|
}
|
|
}
|
|
|
|
if( FBitSet( m_iFlags, FPART_BOUNCE ))
|
|
{
|
|
// bouncy particle
|
|
pmtrace_t pmtrace;
|
|
gEngfuncs.pEventAPI->EV_SetTraceHull( 2 );
|
|
gEngfuncs.pEventAPI->EV_PlayerTrace( m_vecLastOrg, org, PM_STUDIO_IGNORE, -1, &pmtrace );
|
|
|
|
if( pmtrace.fraction != 1.0f )
|
|
{
|
|
// reflect velocity
|
|
time = tr.time - (tr.frametime + tr.frametime * pmtrace.fraction);
|
|
time = (time - m_flTime);
|
|
|
|
vel.x = m_vecVelocity.x;
|
|
vel.y = m_vecVelocity.y;
|
|
vel.z = m_vecVelocity.z + m_vecAccel.z * gravity * time;
|
|
|
|
float d = DotProduct( vel, pmtrace.plane.normal ) * 2.0f;
|
|
m_vecVelocity = vel - pmtrace.plane.normal * d;
|
|
m_vecVelocity *= bound( 0.0f, m_flBounceFactor, 1.0f );
|
|
|
|
// check for stop or slide along the plane
|
|
if( pmtrace.plane.normal.z > 0.0f && m_vecVelocity.z < 1.0f )
|
|
{
|
|
if( pmtrace.plane.normal.z >= 0.7f )
|
|
{
|
|
m_vecVelocity = g_vecZero;
|
|
m_vecAccel = g_vecZero;
|
|
m_iFlags &= ~FPART_BOUNCE;
|
|
}
|
|
else
|
|
{
|
|
// FIXME: check for new plane or free fall
|
|
float dot = DotProduct( m_vecVelocity, pmtrace.plane.normal );
|
|
m_vecVelocity += ( pmtrace.plane.normal * -dot );
|
|
|
|
dot = DotProduct( m_vecAccel, pmtrace.plane.normal );
|
|
m_vecAccel += ( pmtrace.plane.normal * -dot );
|
|
}
|
|
}
|
|
|
|
org = pmtrace.endpos;
|
|
curLength = 1.0f;
|
|
|
|
// reset
|
|
m_flTime = tr.time;
|
|
m_vecColor = curColor;
|
|
m_flAlpha = curAlpha;
|
|
m_flRadius = curRadius;
|
|
m_vecOrigin = org;
|
|
|
|
// don't stretch
|
|
m_iFlags &= ~FPART_STRETCH;
|
|
m_flLengthVelocity = 0.0f;
|
|
m_flLength = curLength;
|
|
}
|
|
}
|
|
|
|
// save current origin if needed
|
|
if( FBitSet( m_iFlags, ( FPART_BOUNCE|FPART_STRETCH )))
|
|
{
|
|
org2 = m_vecLastOrg;
|
|
m_vecLastOrg = org;
|
|
}
|
|
|
|
// vertex lit particle
|
|
if( FBitSet( m_iFlags, FPART_VERTEXLIGHT ))
|
|
{
|
|
Vector light;
|
|
// gather static lighting
|
|
gEngfuncs.pTriAPI->LightAtPoint( org, light );
|
|
light *= (1.0f/255.0f);
|
|
|
|
// gather dynamic lighting
|
|
light += R_LightsForPoint( org, curRadius );
|
|
|
|
// renormalize lighting
|
|
float f = Q_max( Q_max( light.x, light.y ), light.z );
|
|
if( f > 1.0f ) light *= ( 1.0f / f );
|
|
curColor *= light; // multiply to diffuse
|
|
}
|
|
|
|
if( FBitSet( m_iFlags, FPART_INSTANT ))
|
|
{
|
|
// instant particle
|
|
m_flAlphaVelocity = 0.0f;
|
|
m_flAlpha = 0.0f;
|
|
}
|
|
|
|
if( curRadius == 1.0f )
|
|
{
|
|
float scale = 0.0f;
|
|
|
|
// hack a scale up to keep quake particles from disapearing
|
|
scale += (org.x - GetVieworg().x) * GetVForward().x;
|
|
scale += (org.y - GetVieworg().y) * GetVForward().y;
|
|
scale += (org.z - GetVieworg().z) * GetVForward().z;
|
|
if( scale >= 20.0f ) curRadius = 1.0f + scale * 0.004f;
|
|
}
|
|
|
|
Vector axis[3], verts[4];
|
|
Vector absmin, absmax;
|
|
|
|
// prepare to draw
|
|
if( curLength != 1.0f )
|
|
{
|
|
// find orientation vectors
|
|
axis[0] = GetVieworg() - org;
|
|
axis[1] = org2 - org;
|
|
axis[2] = CrossProduct( axis[0], axis[1] );
|
|
|
|
axis[1] = axis[1].Normalize();
|
|
axis[2] = axis[2].Normalize();
|
|
|
|
// find normal
|
|
axis[0] = CrossProduct( axis[1], axis[2] );
|
|
axis[0] = axis[0].Normalize();
|
|
|
|
org3 = org + ( axis[1] * -curLength );
|
|
axis[2] *= m_flRadius;
|
|
|
|
// setup vertexes
|
|
verts[0] = org3 - axis[2];
|
|
verts[1] = org3 + axis[2];
|
|
verts[2] = org + axis[2];
|
|
verts[3] = org - axis[2];
|
|
}
|
|
else
|
|
{
|
|
if( m_flRotation )
|
|
{
|
|
// Rotate it around its normal
|
|
RotatePointAroundVector( axis[1], GetVForward(), GetVLeft(), m_flRotation );
|
|
axis[2] = CrossProduct( GetVForward(), axis[1] );
|
|
|
|
// the normal should point at the viewer
|
|
axis[0] = -GetVForward();
|
|
|
|
// Scale the axes by radius
|
|
axis[1] *= curRadius;
|
|
axis[2] *= curRadius;
|
|
}
|
|
else
|
|
{
|
|
// the normal should point at the viewer
|
|
axis[0] = -GetVForward();
|
|
|
|
// scale the axes by radius
|
|
axis[1] = GetVLeft() * curRadius;
|
|
axis[2] = GetVUp() * curRadius;
|
|
}
|
|
|
|
verts[0] = org + axis[1] - axis[2];
|
|
verts[1] = org + axis[1] + axis[2];
|
|
verts[2] = org - axis[1] + axis[2];
|
|
verts[3] = org - axis[1] - axis[2];
|
|
}
|
|
|
|
ClearBounds( absmin, absmax );
|
|
for( int i = 0; i < 4; i++ )
|
|
AddPointToBounds( verts[i], absmin, absmax );
|
|
#if 0
|
|
GL_Blend( GL_TRUE );
|
|
|
|
if( FBitSet( m_iFlags, FPART_ADDITIVE ))
|
|
pglBlendFunc( GL_SRC_ALPHA, GL_ONE );
|
|
else pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
|
|
|
|
// draw the particle
|
|
GL_BindTexture( GL_TEXTURE0, m_hTexture );
|
|
|
|
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
|
|
|
|
if( FBitSet( m_iFlags, FPART_ADDITIVE ))
|
|
pglColor4f( 1.0f, 1.0f, 1.0f, curAlpha );
|
|
else pglColor4f( curColor.x, curColor.y, curColor.z, curAlpha );
|
|
|
|
pglBegin( GL_QUADS );
|
|
pglTexCoord2f( 0.0f, 1.0f );
|
|
pglVertex3fv( verts[0] );
|
|
|
|
pglTexCoord2f( 0.0f, 0.0f );
|
|
pglVertex3fv( verts[1] );
|
|
|
|
pglTexCoord2f( 1.0f, 0.0f );
|
|
pglVertex3fv( verts[2] );
|
|
|
|
pglTexCoord2f( 1.0f, 1.0f );
|
|
pglVertex3fv( verts[3] );
|
|
pglEnd();
|
|
#else
|
|
CTransEntry entry;
|
|
Vector4D partColor;
|
|
int rendermode;
|
|
|
|
if( FBitSet( m_iFlags, FPART_ADDITIVE ))
|
|
{
|
|
partColor = Vector4D( 1.0f, 1.0f, 1.0f, curAlpha );
|
|
rendermode = kRenderTransAdd;
|
|
}
|
|
else
|
|
{
|
|
partColor = Vector4D( curColor.x, curColor.y, curColor.z, curAlpha );
|
|
rendermode = kRenderTransTexture;
|
|
}
|
|
|
|
entry.SetRenderPrimitive( verts, partColor, m_hTexture, rendermode );
|
|
entry.ComputeViewDistance( absmin, absmax );
|
|
RI->frame.trans_list.AddToTail( entry );
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
CQuakePartSystem :: CQuakePartSystem( void )
|
|
{
|
|
memset( m_pParticles, 0, sizeof( CQuakePart ) * MAX_PARTICLES );
|
|
|
|
m_pFreeParticles = m_pParticles;
|
|
m_pActiveParticles = NULL;
|
|
}
|
|
|
|
CQuakePartSystem :: ~CQuakePartSystem( void )
|
|
{
|
|
}
|
|
|
|
void CQuakePartSystem :: Clear( void )
|
|
{
|
|
m_pFreeParticles = m_pParticles;
|
|
m_pActiveParticles = NULL;
|
|
|
|
for( int i = 0; i < MAX_PARTICLES; i++ )
|
|
m_pParticles[i].pNext = &m_pParticles[i+1];
|
|
|
|
m_pParticles[MAX_PARTICLES-1].pNext = NULL;
|
|
|
|
m_pAllowParticles = CVAR_REGISTER( "cl_particles", "1", FCVAR_ARCHIVE );
|
|
m_pParticleLod = CVAR_REGISTER( "cl_particle_lod", "0", FCVAR_ARCHIVE );
|
|
|
|
// loading TE shaders
|
|
m_hDefaultParticle = FIND_TEXTURE( "*particle" ); // quake particle
|
|
m_hSparks = LOAD_TEXTURE( "gfx/particles/spark", NULL, 0, TF_CLAMP );
|
|
m_hSmoke = LOAD_TEXTURE( "gfx/particles/smoke", NULL, 0, TF_CLAMP );
|
|
m_hWaterSplash = LOAD_TEXTURE( "gfx/particles/splash1", NULL, 0, TF_CLAMP );
|
|
|
|
ParsePartInfos( "gfx/particles/effects.txt" );
|
|
}
|
|
|
|
void CQuakePartSystem :: ParsePartInfos( const char *filename )
|
|
{
|
|
// we parse our effects each call of VidInit because we need to keep textures and sprites an actual
|
|
|
|
ALERT( at_aiconsole, "loading %s\n", filename );
|
|
|
|
char *afile = (char *)gEngfuncs.COM_LoadFile( (char *)filename, 5, NULL );
|
|
|
|
if( !afile )
|
|
{
|
|
ALERT( at_error, "Cannot open file %s\n", filename );
|
|
return;
|
|
}
|
|
|
|
char *pfile = afile;
|
|
char token[256];
|
|
m_iNumPartInfo = 0;
|
|
|
|
memset( &m_pPartInfo, 0, sizeof( m_pPartInfo ));
|
|
|
|
while( pfile != NULL )
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile ) break;
|
|
|
|
if( m_iNumPartInfo >= MAX_PARTINFOS )
|
|
{
|
|
ALERT ( at_error, "particle effects info limit exceeded %d > %d\n", m_iNumPartInfo, MAX_PARTINFOS );
|
|
break;
|
|
}
|
|
|
|
CQuakePartInfo *pCur = &m_pPartInfo[m_iNumPartInfo];
|
|
|
|
// read the effect name
|
|
Q_strncpy( pCur->m_szName, token, sizeof( pCur->m_szName ));
|
|
|
|
// read opening brace
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile ) break;
|
|
|
|
if( token[0] != '{' )
|
|
{
|
|
ALERT( at_error, "found %s when expecting {\n", token );
|
|
break;
|
|
}
|
|
|
|
if( !ParsePartInfo( pCur, pfile ))
|
|
break; // something bad happens
|
|
|
|
}
|
|
|
|
gEngfuncs.COM_FreeFile( afile );
|
|
ALERT( at_aiconsole, "%d effects parsed\n", m_iNumPartInfo );
|
|
}
|
|
|
|
CQuakePartInfo *CQuakePartSystem :: FindPartInfo( const char *name )
|
|
{
|
|
for( int i = 0; i < m_iNumPartInfo; i++ )
|
|
{
|
|
if( !Q_stricmp( m_pPartInfo[i].m_szName, name ))
|
|
return &m_pPartInfo[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void CQuakePartSystem :: CreateEffect( const char *name, const Vector &origin, const Vector &normal )
|
|
{
|
|
if( !g_fRenderInitialized || !m_pAllowParticles->value )
|
|
return;
|
|
|
|
if( !name || !*name )
|
|
return;
|
|
|
|
CQuakePartInfo *pInfo = FindPartInfo( name );
|
|
int m_hTexture = m_hDefaultParticle;
|
|
CQuakePart src;
|
|
|
|
if( !pInfo )
|
|
{
|
|
ALERT( at_warning, "QuakeParticle: couldn't find effect '%s'\n", name );
|
|
return;
|
|
}
|
|
|
|
// sparks
|
|
int flags = pInfo->flags;
|
|
|
|
if( FBitSet( flags, FPART_NOTWATER ) && POINT_CONTENTS( (float *)&origin ) == CONTENTS_WATER )
|
|
return;
|
|
|
|
int count = bound( 1, pInfo->count.Random(), 1024 ); // don't alloc too many particles
|
|
|
|
for( int i = 0; i < count; i++ )
|
|
{
|
|
if( pInfo->m_pSprite )
|
|
{
|
|
int frame = bound( 0, pInfo->frame.Random(), pInfo->m_pSprite->numframes - 1 );
|
|
mspriteframe_t *pframe = R_GetSpriteFrame( pInfo->m_pSprite, frame );
|
|
if( pframe ) m_hTexture = pframe->gl_texturenum;
|
|
}
|
|
else m_hTexture = pInfo->m_hTexture;
|
|
|
|
if( pInfo->normal == NORMAL_OFFSET || pInfo->normal == NORMAL_OFS_DIR )
|
|
{
|
|
src.m_vecOrigin.x = origin.x + pInfo->offset[0].Random() * normal.x;
|
|
src.m_vecOrigin.y = origin.y + pInfo->offset[1].Random() * normal.y;
|
|
src.m_vecOrigin.z = origin.z + pInfo->offset[2].Random() * normal.z;
|
|
}
|
|
else
|
|
{
|
|
src.m_vecOrigin.x = origin.x + pInfo->offset[0].Random();
|
|
src.m_vecOrigin.y = origin.y + pInfo->offset[1].Random();
|
|
src.m_vecOrigin.z = origin.z + pInfo->offset[2].Random();
|
|
}
|
|
|
|
if( pInfo->normal == NORMAL_DIRECTION || pInfo->normal == NORMAL_OFS_DIR )
|
|
{
|
|
src.m_vecVelocity.x = normal.x * pInfo->velocity[0].Random();
|
|
src.m_vecVelocity.y = normal.y * pInfo->velocity[1].Random();
|
|
src.m_vecVelocity.z = normal.z * pInfo->velocity[2].Random();
|
|
}
|
|
else
|
|
{
|
|
src.m_vecVelocity.x = pInfo->velocity[0].Random();
|
|
src.m_vecVelocity.y = pInfo->velocity[1].Random();
|
|
src.m_vecVelocity.z = pInfo->velocity[2].Random();
|
|
}
|
|
|
|
src.m_vecAccel.x = pInfo->accel[0].Random();
|
|
src.m_vecAccel.y = pInfo->accel[1].Random();
|
|
src.m_vecAccel.z = pInfo->accel[2].Random();
|
|
src.m_vecColor.x = pInfo->color[0].Random();
|
|
src.m_vecColor.y = pInfo->color[1].Random();
|
|
src.m_vecColor.z = pInfo->color[2].Random();
|
|
src.m_vecColorVelocity.x = pInfo->colorVel[0].Random();
|
|
src.m_vecColorVelocity.y = pInfo->colorVel[1].Random();
|
|
src.m_vecColorVelocity.z = pInfo->colorVel[2].Random();
|
|
src.m_flAlpha = pInfo->alpha.Random();
|
|
src.m_flAlphaVelocity = pInfo->alphaVel.Random();
|
|
src.m_flRadius = pInfo->radius.Random();
|
|
src.m_flRadiusVelocity = pInfo->radiusVel.Random();
|
|
src.m_flLength = pInfo->length.Random();
|
|
src.m_flLengthVelocity = pInfo->lengthVel.Random();
|
|
src.m_flRotation = pInfo->rotation.Random();
|
|
src.m_flBounceFactor = pInfo->bounce.Random();
|
|
|
|
if( !AddParticle( &src, m_hTexture, flags ))
|
|
return; // out of particles?
|
|
}
|
|
}
|
|
|
|
bool CQuakePartSystem :: ParseRandomVector( char *&pfile, RandomRange out[3] )
|
|
{
|
|
char token[256];
|
|
|
|
for( int i = 0; i < 3 && pfile != NULL; i++ )
|
|
{
|
|
pfile = COM_ParseLine( pfile, token );
|
|
out[i] = RandomRange( token );
|
|
}
|
|
|
|
return (i == 3) ? true : false;
|
|
}
|
|
|
|
int CQuakePartSystem :: ParseParticleFlags( char *pfile )
|
|
{
|
|
char token[256];
|
|
int iFlags = 0;
|
|
|
|
if( !pfile || !*pfile )
|
|
return iFlags;
|
|
|
|
while( pfile != NULL )
|
|
{
|
|
pfile = COM_ParseLine( pfile, token );
|
|
|
|
if( !Q_stricmp( token, "Bounce" ))
|
|
iFlags |= FPART_BOUNCE;
|
|
else if( !Q_stricmp( token, "Friction" ))
|
|
iFlags |= FPART_FRICTION;
|
|
else if( !Q_stricmp( token, "Light" ))
|
|
iFlags |= FPART_VERTEXLIGHT;
|
|
else if( !Q_stricmp( token, "Stretch" ))
|
|
iFlags |= FPART_STRETCH;
|
|
else if( !Q_stricmp( token, "Underwater" ))
|
|
iFlags |= FPART_UNDERWATER;
|
|
else if( !Q_stricmp( token, "Instant" ))
|
|
iFlags |= FPART_INSTANT;
|
|
else if( !Q_stricmp( token, "Additive" ))
|
|
iFlags |= FPART_ADDITIVE;
|
|
else if( !Q_stricmp( token, "NotInWater" ))
|
|
iFlags |= FPART_NOTWATER;
|
|
else if( pfile && token[0] != '|' )
|
|
ALERT( at_warning, "unknown value %s for 'flags'\n", token );
|
|
}
|
|
|
|
return iFlags;
|
|
}
|
|
|
|
bool CQuakePartSystem :: ParsePartInfo( CQuakePartInfo *info, char *&pfile )
|
|
{
|
|
char token[256];
|
|
|
|
while( pfile != NULL )
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "EOF without closing brace\n" );
|
|
return false;
|
|
}
|
|
|
|
// description end goto next material
|
|
if( token[0] == '}' )
|
|
{
|
|
m_iNumPartInfo++;
|
|
return true;
|
|
}
|
|
else if( !Q_stricmp( token, "texture" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'texture'\n" );
|
|
break;
|
|
}
|
|
|
|
const char *ext = COM_FileExtension( token );
|
|
|
|
if( !Q_stricmp( ext, "tga" ) || !Q_stricmp( ext, "dds" ))
|
|
info->m_hTexture = LOAD_TEXTURE( token, NULL, 0, TF_CLAMP );
|
|
else if( !Q_stricmp( ext, "spr" ))
|
|
info->m_pSprite = (model_t *)gEngfuncs.GetSpritePointer( SPR_Load( token ));
|
|
|
|
if( !info->m_hTexture && !info->m_pSprite )
|
|
{
|
|
ALERT( at_error, "couldn't load texture for effect %s. using default particle\n", info->m_szName );
|
|
info->m_hTexture = m_hDefaultParticle;
|
|
}
|
|
}
|
|
else if( !Q_stricmp( token, "offset" ))
|
|
{
|
|
if( !ParseRandomVector( pfile, info->offset ))
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'offset'\n" );
|
|
break;
|
|
}
|
|
}
|
|
else if( !Q_stricmp( token, "velocity" ))
|
|
{
|
|
if( !ParseRandomVector( pfile, info->velocity ))
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'velocity'\n" );
|
|
break;
|
|
}
|
|
}
|
|
else if( !Q_stricmp( token, "accel" ))
|
|
{
|
|
if( !ParseRandomVector( pfile, info->accel ))
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'accel'\n" );
|
|
break;
|
|
}
|
|
}
|
|
else if( !Q_stricmp( token, "color" ))
|
|
{
|
|
if( !ParseRandomVector( pfile, info->color ))
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'color'\n" );
|
|
break;
|
|
}
|
|
}
|
|
else if( !Q_stricmp( token, "colorVelocity" ))
|
|
{
|
|
if( !ParseRandomVector( pfile, info->colorVel ))
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'colorVelocity'\n" );
|
|
break;
|
|
}
|
|
}
|
|
else if( !Q_stricmp( token, "alpha" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'alpha'\n" );
|
|
break;
|
|
}
|
|
|
|
info->alpha = RandomRange( token );
|
|
}
|
|
else if( !Q_stricmp( token, "alphaVelocity" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'alphaVelocity'\n" );
|
|
break;
|
|
}
|
|
|
|
info->alphaVel = RandomRange( token );
|
|
}
|
|
else if( !Q_stricmp( token, "radius" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'radius'\n" );
|
|
break;
|
|
}
|
|
|
|
info->radius = RandomRange( token );
|
|
}
|
|
else if( !Q_stricmp( token, "radiusVelocity" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'radiusVelocity'\n" );
|
|
break;
|
|
}
|
|
|
|
info->radiusVel = RandomRange( token );
|
|
}
|
|
else if( !Q_stricmp( token, "length" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'length'\n" );
|
|
break;
|
|
}
|
|
|
|
info->length = RandomRange( token );
|
|
}
|
|
else if( !Q_stricmp( token, "lengthVelocity" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'lengthVelocity'\n" );
|
|
break;
|
|
}
|
|
|
|
info->lengthVel = RandomRange( token );
|
|
}
|
|
else if( !Q_stricmp( token, "rotation" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'rotation'\n" );
|
|
break;
|
|
}
|
|
|
|
info->rotation = RandomRange( token );
|
|
}
|
|
else if( !Q_stricmp( token, "bounceFactor" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'bounceFactor'\n" );
|
|
break;
|
|
}
|
|
|
|
info->bounce = RandomRange( token );
|
|
}
|
|
else if( !Q_stricmp( token, "frame" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'frame'\n" );
|
|
break;
|
|
}
|
|
|
|
info->frame = RandomRange( token );
|
|
}
|
|
else if( !Q_stricmp( token, "count" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'count'\n" );
|
|
break;
|
|
}
|
|
|
|
info->count = RandomRange( token );
|
|
}
|
|
else if( !Q_stricmp( token, "flags" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'flags'\n" );
|
|
break;
|
|
}
|
|
|
|
info->flags = ParseParticleFlags( token );
|
|
}
|
|
else if( !Q_stricmp( token, "useNormal" ))
|
|
{
|
|
pfile = COM_ParseFile( pfile, token );
|
|
if( !pfile )
|
|
{
|
|
ALERT( at_error, "hit EOF while parsing 'useNormal'\n" );
|
|
break;
|
|
}
|
|
|
|
if( !Q_stricmp( token, "ignore" ))
|
|
info->normal = NORMAL_IGNORE;
|
|
else if( !Q_stricmp( token, "ofs" ))
|
|
info->normal = NORMAL_OFFSET;
|
|
else if( !Q_stricmp( token, "dir" ))
|
|
info->normal = NORMAL_DIRECTION;
|
|
else if( !Q_stricmp( token, "ofs+dir" ))
|
|
info->normal = NORMAL_OFS_DIR;
|
|
else if( !Q_stricmp( token, "dir+ofs" ))
|
|
info->normal = NORMAL_OFS_DIR;
|
|
else ALERT( at_warning, "Unknown 'useNormal' key '%s'\n", token );
|
|
}
|
|
else ALERT( at_warning, "Unknown effects token %s\n", token );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CQuakePartSystem :: FreeParticle( CQuakePart *pCur )
|
|
{
|
|
pCur->pNext = m_pFreeParticles;
|
|
m_pFreeParticles = pCur;
|
|
}
|
|
|
|
CQuakePart *CQuakePartSystem :: AllocParticle( void )
|
|
{
|
|
CQuakePart *pCur;
|
|
|
|
if( !m_pFreeParticles )
|
|
{
|
|
ALERT( at_console, "Overflow %d particles\n", MAX_PARTICLES );
|
|
return NULL;
|
|
}
|
|
|
|
if( m_pParticleLod->value > 1.0f )
|
|
{
|
|
if( !( RANDOM_LONG( 0, 1 ) % (int)m_pParticleLod->value ))
|
|
return NULL;
|
|
}
|
|
|
|
pCur = m_pFreeParticles;
|
|
m_pFreeParticles = pCur->pNext;
|
|
pCur->pNext = m_pActiveParticles;
|
|
m_pActiveParticles = pCur;
|
|
|
|
return pCur;
|
|
}
|
|
|
|
void CQuakePartSystem :: Update( void )
|
|
{
|
|
CQuakePart *pCur, *pNext;
|
|
CQuakePart *pActive = NULL, *pTail = NULL;
|
|
|
|
if( !m_pAllowParticles->value )
|
|
return;
|
|
|
|
if( FBitSet( RI->params, ( RP_ENVVIEW|RP_SKYVIEW )))
|
|
return;
|
|
|
|
GL_DepthMask( GL_FALSE );
|
|
|
|
float gravity = tr.frametime * tr.gravity;
|
|
|
|
for( pCur = m_pActiveParticles; pCur != NULL; pCur = pNext )
|
|
{
|
|
// grab next now, so if the particle is freed we still have it
|
|
pNext = pCur->pNext;
|
|
|
|
if( !pCur->Evaluate( gravity ))
|
|
{
|
|
FreeParticle( pCur );
|
|
continue;
|
|
}
|
|
|
|
pCur->pNext = NULL;
|
|
|
|
if( !pTail )
|
|
{
|
|
pActive = pTail = pCur;
|
|
}
|
|
else
|
|
{
|
|
pTail->pNext = pCur;
|
|
pTail = pCur;
|
|
}
|
|
}
|
|
|
|
m_pActiveParticles = pActive;
|
|
}
|
|
|
|
bool CQuakePartSystem :: AddParticle( CQuakePart *src, int texture, int flags )
|
|
{
|
|
if( !src ) return false;
|
|
|
|
CQuakePart *dst = AllocParticle();
|
|
|
|
if( !dst ) return false;
|
|
|
|
if( texture ) dst->m_hTexture = texture;
|
|
else dst->m_hTexture = m_hDefaultParticle;
|
|
dst->m_flTime = tr.time;
|
|
dst->m_iFlags = flags;
|
|
|
|
dst->m_vecOrigin = src->m_vecOrigin;
|
|
dst->m_vecVelocity = src->m_vecVelocity;
|
|
dst->m_vecAccel = src->m_vecAccel;
|
|
dst->m_vecColor = src->m_vecColor;
|
|
dst->m_vecColorVelocity = src->m_vecColorVelocity;
|
|
dst->m_flAlpha = src->m_flAlpha;
|
|
|
|
dst->m_flRadius = src->m_flRadius;
|
|
dst->m_flLength = src->m_flLength;
|
|
dst->m_flRotation = src->m_flRotation;
|
|
dst->m_flAlphaVelocity = src->m_flAlphaVelocity;
|
|
dst->m_flRadiusVelocity = src->m_flRadiusVelocity;
|
|
dst->m_flLengthVelocity = src->m_flLengthVelocity;
|
|
dst->m_flBounceFactor = src->m_flBounceFactor;
|
|
|
|
// needs to save old origin
|
|
if( FBitSet( flags, ( FPART_BOUNCE|FPART_FRICTION )))
|
|
dst->m_vecLastOrg = dst->m_vecOrigin;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CL_ExplosionParticles
|
|
=================
|
|
*/
|
|
void CQuakePartSystem :: ExplosionParticles( const Vector &pos )
|
|
{
|
|
CQuakePart src;
|
|
int flags;
|
|
|
|
if( !g_fRenderInitialized || !m_pAllowParticles->value )
|
|
return;
|
|
|
|
flags = (FPART_STRETCH|FPART_BOUNCE|FPART_FRICTION);
|
|
|
|
for( int i = 0; i < 384; i++ )
|
|
{
|
|
src.m_vecOrigin.x = pos.x + RANDOM_LONG( -16, 16 );
|
|
src.m_vecOrigin.y = pos.y + RANDOM_LONG( -16, 16 );
|
|
src.m_vecOrigin.z = pos.z + RANDOM_LONG( -16, 16 );
|
|
src.m_vecVelocity.x = RANDOM_LONG( -256, 256 );
|
|
src.m_vecVelocity.y = RANDOM_LONG( -256, 256 );
|
|
src.m_vecVelocity.z = RANDOM_LONG( -256, 256 );
|
|
src.m_vecAccel.x = src.m_vecAccel.y = 0;
|
|
src.m_vecAccel.z = -60 + RANDOM_FLOAT( -30, 30 );
|
|
src.m_vecColor = Vector( 1, 1, 1 );
|
|
src.m_vecColorVelocity = Vector( 0, 0, 0 );
|
|
src.m_flAlpha = 1.0;
|
|
src.m_flAlphaVelocity = -3.0;
|
|
src.m_flRadius = 0.5 + RANDOM_FLOAT( -0.2, 0.2 );
|
|
src.m_flRadiusVelocity = 0;
|
|
src.m_flLength = 8 + RANDOM_FLOAT( -4, 4 );
|
|
src.m_flLengthVelocity = 8 + RANDOM_FLOAT( -4, 4 );
|
|
src.m_flRotation = 0;
|
|
src.m_flBounceFactor = 0.2;
|
|
|
|
if( !AddParticle( &src, m_hSparks, flags ))
|
|
return;
|
|
}
|
|
|
|
// smoke
|
|
flags = FPART_VERTEXLIGHT;
|
|
|
|
for( i = 0; i < 5; i++ )
|
|
{
|
|
src.m_vecOrigin.x = pos.x + RANDOM_FLOAT( -10, 10 );
|
|
src.m_vecOrigin.y = pos.y + RANDOM_FLOAT( -10, 10 );
|
|
src.m_vecOrigin.z = pos.z + RANDOM_FLOAT( -10, 10 );
|
|
src.m_vecVelocity.x = RANDOM_FLOAT( -10, 10 );
|
|
src.m_vecVelocity.y = RANDOM_FLOAT( -10, 10 );
|
|
src.m_vecVelocity.z = RANDOM_FLOAT( -10, 10 ) + RANDOM_FLOAT( -5, 5 ) + 25;
|
|
src.m_vecAccel = Vector( 0, 0, 0 );
|
|
src.m_vecColor = Vector( 0, 0, 0 );
|
|
src.m_vecColorVelocity = Vector( 0.75, 0.75, 0.75 );
|
|
src.m_flAlpha = 0.5;
|
|
src.m_flAlphaVelocity = RANDOM_FLOAT( -0.1, -0.2 );
|
|
src.m_flRadius = 30 + RANDOM_FLOAT( -15, 15 );
|
|
src.m_flRadiusVelocity = 15 + RANDOM_FLOAT( -7.5, 7.5 );
|
|
src.m_flLength = 1;
|
|
src.m_flLengthVelocity = 0;
|
|
src.m_flRotation = RANDOM_LONG( 0, 360 );
|
|
|
|
if( !AddParticle( &src, m_hSmoke, flags ))
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CL_BulletParticles
|
|
=================
|
|
*/
|
|
void CQuakePartSystem :: SparkParticles( const Vector &org, const Vector &dir )
|
|
{
|
|
CQuakePart src;
|
|
|
|
if( !g_fRenderInitialized || !m_pAllowParticles->value )
|
|
return;
|
|
|
|
// sparks
|
|
int flags = (FPART_STRETCH|FPART_BOUNCE|FPART_FRICTION);
|
|
|
|
for( int i = 0; i < 16; i++ )
|
|
{
|
|
src.m_vecOrigin.x = org[0] + dir[0] * 2 + RANDOM_FLOAT( -1, 1 );
|
|
src.m_vecOrigin.y = org[1] + dir[1] * 2 + RANDOM_FLOAT( -1, 1 );
|
|
src.m_vecOrigin.z = org[2] + dir[2] * 2 + RANDOM_FLOAT( -1, 1 );
|
|
src.m_vecVelocity.x = dir[0] * 180 + RANDOM_FLOAT( -60, 60 );
|
|
src.m_vecVelocity.y = dir[1] * 180 + RANDOM_FLOAT( -60, 60 );
|
|
src.m_vecVelocity.z = dir[2] * 180 + RANDOM_FLOAT( -60, 60 );
|
|
src.m_vecAccel.x = src.m_vecAccel.y = 0;
|
|
src.m_vecAccel.z = -120 + RANDOM_FLOAT( -60, 60 );
|
|
src.m_vecColor = Vector( 1.0, 1.0f, 1.0f );
|
|
src.m_vecColorVelocity = Vector( 0, 0, 0 );
|
|
src.m_flAlpha = 1.0;
|
|
src.m_flAlphaVelocity = -8.0;
|
|
src.m_flRadius = 0.4 + RANDOM_FLOAT( -0.2, 0.2 );
|
|
src.m_flRadiusVelocity = 0;
|
|
src.m_flLength = 8 + RANDOM_FLOAT( -4, 4 );
|
|
src.m_flLengthVelocity = 8 + RANDOM_FLOAT( -4, 4 );
|
|
src.m_flRotation = 0;
|
|
src.m_flBounceFactor = 0.2;
|
|
|
|
if( !AddParticle( &src, m_hSparks, flags ))
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CL_RicochetSparks
|
|
=================
|
|
*/
|
|
void CQuakePartSystem :: RicochetSparks( const Vector &org, float scale )
|
|
{
|
|
CQuakePart src;
|
|
|
|
if( !g_fRenderInitialized || !m_pAllowParticles->value )
|
|
return;
|
|
|
|
// sparks
|
|
int flags = (FPART_STRETCH|FPART_BOUNCE|FPART_FRICTION);
|
|
|
|
for( int i = 0; i < 16; i++ )
|
|
{
|
|
src.m_vecOrigin.x = org[0] + RANDOM_FLOAT( -1, 1 );
|
|
src.m_vecOrigin.y = org[1] + RANDOM_FLOAT( -1, 1 );
|
|
src.m_vecOrigin.z = org[2] + RANDOM_FLOAT( -1, 1 );
|
|
src.m_vecVelocity.x = RANDOM_FLOAT( -60, 60 );
|
|
src.m_vecVelocity.y = RANDOM_FLOAT( -60, 60 );
|
|
src.m_vecVelocity.z = RANDOM_FLOAT( -60, 60 );
|
|
src.m_vecAccel.x = src.m_vecAccel.y = 0;
|
|
src.m_vecAccel.z = -120 + RANDOM_FLOAT( -60, 60 );
|
|
src.m_vecColor = Vector( 1.0, 1.0f, 1.0f );
|
|
src.m_vecColorVelocity = Vector( 0, 0, 0 );
|
|
src.m_flAlpha = 1.0;
|
|
src.m_flAlphaVelocity = -8.0;
|
|
src.m_flRadius = scale + RANDOM_FLOAT( -0.2, 0.2 );
|
|
src.m_flRadiusVelocity = 0;
|
|
src.m_flLength = scale + RANDOM_FLOAT( -0.2, 0.2 );
|
|
src.m_flLengthVelocity = scale + RANDOM_FLOAT( -0.2, 0.2 );
|
|
src.m_flRotation = 0;
|
|
src.m_flBounceFactor = 0.2;
|
|
|
|
if( !AddParticle( &src, m_hSparks, flags ))
|
|
return;
|
|
}
|
|
}
|
|
|
|
void CQuakePartSystem :: SmokeParticles( const Vector &pos, int count )
|
|
{
|
|
CQuakePart src;
|
|
|
|
if( !g_fRenderInitialized || !m_pAllowParticles->value )
|
|
return;
|
|
|
|
// smoke
|
|
int flags = FPART_VERTEXLIGHT;
|
|
|
|
for( int i = 0; i < count; i++ )
|
|
{
|
|
src.m_vecOrigin.x = pos.x + RANDOM_FLOAT( -10, 10 );
|
|
src.m_vecOrigin.y = pos.y + RANDOM_FLOAT( -10, 10 );
|
|
src.m_vecOrigin.z = pos.z + RANDOM_FLOAT( -10, 10 );
|
|
src.m_vecVelocity.x = RANDOM_FLOAT( -10, 10 );
|
|
src.m_vecVelocity.y = RANDOM_FLOAT( -10, 10 );
|
|
src.m_vecVelocity.z = RANDOM_FLOAT( -10, 10 ) + RANDOM_FLOAT( -5, 5 ) + 25;
|
|
src.m_vecAccel = Vector( 0, 0, 0 );
|
|
src.m_vecColor = Vector( 0, 0, 0 );
|
|
src.m_vecColorVelocity = Vector( 0.75, 0.75, 0.75 );
|
|
src.m_flAlpha = 0.5;
|
|
src.m_flAlphaVelocity = RANDOM_FLOAT( -0.1, -0.15 );
|
|
src.m_flRadius = 30 + RANDOM_FLOAT( -15, 15 );
|
|
src.m_flRadiusVelocity = 15 + RANDOM_FLOAT( -7.5, 7.5 );
|
|
src.m_flLength = 1;
|
|
src.m_flLengthVelocity = 0;
|
|
src.m_flRotation = RANDOM_LONG( 0, 360 );
|
|
|
|
if( !AddParticle( &src, m_hSmoke, flags ))
|
|
return;
|
|
}
|
|
}
|
|
|
|
void CQuakePartSystem :: GunSmoke( const Vector &pos, int count )
|
|
{
|
|
CQuakePart src;
|
|
|
|
if( !g_fRenderInitialized || !m_pAllowParticles->value )
|
|
return;
|
|
|
|
// smoke
|
|
int flags = FPART_VERTEXLIGHT;
|
|
|
|
for( int i = 0; i < count; i++ )
|
|
{
|
|
src.m_vecOrigin.x = pos.x + RANDOM_FLOAT( -0.1f, 0.1f );
|
|
src.m_vecOrigin.y = pos.y + RANDOM_FLOAT( -0.1f, 0.1f );
|
|
src.m_vecOrigin.z = pos.z + RANDOM_FLOAT( -0.1f, 0.1f );
|
|
src.m_vecVelocity.x = RANDOM_FLOAT( -5.1f, 5.1f );
|
|
src.m_vecVelocity.y = RANDOM_FLOAT( -5.1f, 5.1f );
|
|
src.m_vecVelocity.z = RANDOM_FLOAT( -5.1f, 5.1f );
|
|
src.m_vecAccel = Vector( 0, 0, 0 );
|
|
src.m_vecColor = Vector( 1.0f, 1.0f, 1.0f );
|
|
src.m_vecColorVelocity = g_vecZero;
|
|
src.m_flAlpha = 0.5;
|
|
src.m_flAlphaVelocity = RANDOM_FLOAT( -0.2f, -0.4f );
|
|
src.m_flRadius = RANDOM_FLOAT( 4.0f, 6.0f );
|
|
src.m_flRadiusVelocity = 2.0f + RANDOM_FLOAT( -0.5, 0.5 );
|
|
src.m_flLength = 1;
|
|
src.m_flLengthVelocity = 0;
|
|
src.m_flRotation = RANDOM_LONG( 0, 360 );
|
|
|
|
if( !AddParticle( &src, m_hSmoke, flags ))
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CL_BulletParticles
|
|
=================
|
|
*/
|
|
void CQuakePartSystem :: BulletParticles( const Vector &org, const Vector &dir )
|
|
{
|
|
CQuakePart src;
|
|
int cnt, count;
|
|
|
|
if( !g_fRenderInitialized || !m_pAllowParticles->value )
|
|
return;
|
|
|
|
count = RANDOM_LONG( 3, 8 );
|
|
cnt = POINT_CONTENTS( (float *)&org );
|
|
|
|
if( cnt == CONTENTS_WATER )
|
|
return;
|
|
|
|
// sparks
|
|
int flags = (FPART_STRETCH|FPART_BOUNCE|FPART_FRICTION|FPART_ADDITIVE);
|
|
|
|
for( int i = 0; i < count; i++ )
|
|
{
|
|
src.m_vecOrigin.x = org[0] + dir[0] * 2 + RANDOM_FLOAT( -1, 1 );
|
|
src.m_vecOrigin.y = org[1] + dir[1] * 2 + RANDOM_FLOAT( -1, 1 );
|
|
src.m_vecOrigin.z = org[2] + dir[2] * 2 + RANDOM_FLOAT( -1, 1 );
|
|
src.m_vecVelocity.x = dir[0] * 180 + RANDOM_FLOAT( -60, 60 );
|
|
src.m_vecVelocity.y = dir[1] * 180 + RANDOM_FLOAT( -60, 60 );
|
|
src.m_vecVelocity.z = dir[2] * 180 + RANDOM_FLOAT( -60, 60 );
|
|
src.m_vecAccel.x = src.m_vecAccel.y = 0;
|
|
src.m_vecAccel.z = -120 + RANDOM_FLOAT( -60, 60 );
|
|
src.m_vecColor = Vector( 1.0, 1.0f, 1.0f );
|
|
src.m_vecColorVelocity = Vector( 0, 0, 0 );
|
|
src.m_flAlpha = 1.0;
|
|
src.m_flAlphaVelocity = -8.0;
|
|
src.m_flRadius = 0.4 + RANDOM_FLOAT( -0.2, 0.2 );
|
|
src.m_flRadiusVelocity = 0;
|
|
src.m_flLength = 8 + RANDOM_FLOAT( -4, 4 );
|
|
src.m_flLengthVelocity = 8 + RANDOM_FLOAT( -4, 4 );
|
|
src.m_flRotation = 0;
|
|
src.m_flBounceFactor = 0.2;
|
|
|
|
if( !AddParticle( &src, m_hSparks, flags ))
|
|
return;
|
|
}
|
|
|
|
// smoke
|
|
flags = FPART_VERTEXLIGHT;
|
|
|
|
for( i = 0; i < 3; i++ )
|
|
{
|
|
src.m_vecOrigin.x = org[0] + dir[0] * 5 + RANDOM_FLOAT( -1, 1 );
|
|
src.m_vecOrigin.y = org[1] + dir[1] * 5 + RANDOM_FLOAT( -1, 1 );
|
|
src.m_vecOrigin.z = org[2] + dir[2] * 5 + RANDOM_FLOAT( -1, 1 );
|
|
src.m_vecVelocity.x = RANDOM_FLOAT( -2.5, 2.5 );
|
|
src.m_vecVelocity.y = RANDOM_FLOAT( -2.5, 2.5 );
|
|
src.m_vecVelocity.z = RANDOM_FLOAT( -2.5, 2.5 ) + (25 + RANDOM_FLOAT( -5, 5 ));
|
|
src.m_vecAccel = Vector( 0, 0, 0 );
|
|
src.m_vecColor = Vector( 0.4, 0.4, 0.4 );
|
|
src.m_vecColorVelocity = Vector( 0.2, 0.2, 0.2 );
|
|
src.m_flAlpha = 0.5;
|
|
src.m_flAlphaVelocity = -(0.4 + RANDOM_FLOAT( 0, 0.2 ));
|
|
src.m_flRadius = 3 + RANDOM_FLOAT( -1.5, 1.5 );
|
|
src.m_flRadiusVelocity = 5 + RANDOM_FLOAT( -2.5, 2.5 );
|
|
src.m_flLength = 1;
|
|
src.m_flLengthVelocity = 0;
|
|
src.m_flRotation = RANDOM_LONG( 0, 360 );
|
|
|
|
if( !AddParticle( &src, m_hSmoke, flags ))
|
|
return;
|
|
}
|
|
} |