This repository has been archived on 2022-06-27. You can view files and clone it, but cannot push or open issues or pull requests.
Xash3DArchive/engine/client/gl_rpart.c

1676 lines
36 KiB
C

/*
cl_part.c - particles and tracers
Copyright (C) 2010 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 "common.h"
#include "client.h"
#include "gl_local.h"
#include "r_efx.h"
#include "event_flags.h"
#include "entity_types.h"
#include "triangleapi.h"
#include "cl_tent.h"
#include "studio.h"
/*
==============================================================
PARTICLES MANAGEMENT
==============================================================
*/
#define SPARK_COLORCOUNT 9
#define TRACER_WIDTH 0.5f
#define SIMSHIFT 10
// particle velocities
static const float cl_avertexnormals[NUMVERTEXNORMALS][3] =
{
#include "anorms.h"
};
// particle ramps
static int ramp1[8] = { 0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61 };
static int ramp2[8] = { 0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66 };
static int ramp3[6] = { 0x6d, 0x6b, 6, 5, 4, 3 };
static int boxpnt[6][4] =
{
{ 0, 4, 6, 2 }, // +X
{ 0, 1, 5, 4 }, // +Y
{ 0, 2, 3, 1 }, // +Z
{ 7, 5, 1, 3 }, // -X
{ 7, 3, 2, 6 }, // -Y
{ 7, 6, 4, 5 }, // -Z
};
static rgb_t gTracerColors[] =
{
{ 255, 255, 255 }, // White
{ 255, 0, 0 }, // Red
{ 0, 255, 0 }, // Green
{ 0, 0, 255 }, // Blue
{ 0, 0, 0 }, // Tracer default, filled in from cvars, etc.
{ 255, 167, 17 }, // Yellow-orange sparks
{ 255, 130, 90 }, // Yellowish streaks (garg)
{ 55, 60, 144 }, // Blue egon streak
{ 255, 130, 90 }, // More Yellowish streaks (garg)
{ 255, 140, 90 }, // More Yellowish streaks (garg)
{ 200, 130, 90 }, // More red streaks (garg)
{ 255, 120, 70 }, // Darker red streaks (garg)
};
static int gSparkRamp[SPARK_COLORCOUNT][3] =
{
{ 255, 255, 255 },
{ 255, 247, 199 },
{ 255, 243, 147 },
{ 255, 243, 27 },
{ 239, 203, 31 },
{ 223, 171, 39 },
{ 207, 143, 43 },
{ 127, 59, 43 },
{ 35, 19, 7 }
};
convar_t *tracerred;
convar_t *tracergreen;
convar_t *tracerblue;
convar_t *traceralpha;
convar_t *tracerspeed;
convar_t *tracerlength;
convar_t *traceroffset;
particle_t *cl_active_particles;
particle_t *cl_free_particles;
particle_t *cl_particles = NULL; // particle pool
static vec3_t cl_avelocities[NUMVERTEXNORMALS];
#define COL_SUM( pal, clr ) (pal - clr) * (pal - clr)
/*
================
CL_LookupColor
find nearest color in particle palette
================
*/
short CL_LookupColor( byte r, byte g, byte b )
{
int i, fi, best_color = 0;
int f_min = 1000000;
byte *pal;
#if 0
// apply gamma-correction
r = clgame.ds.gammaTable[r];
g = clgame.ds.gammaTable[g];
b = clgame.ds.gammaTable[b];
#endif
for( i = 0; i < 256; i++ )
{
pal = clgame.palette[i];
fi = 30 * COL_SUM( pal[0], r ) + 59 * COL_SUM( pal[1], g ) + 11 * COL_SUM( pal[2], b );
if( fi < f_min )
{
best_color = i,
f_min = fi;
}
}
// make sure what color is in-range
return (word)(best_color & 255);
}
/*
================
CL_GetPackedColor
in hardware mode does nothing
================
*/
void CL_GetPackedColor( short *packed, short color )
{
if( packed ) *packed = 0;
}
/*
================
CL_InitParticles
================
*/
void CL_InitParticles( void )
{
int i;
cl_particles = Mem_Alloc( cls.mempool, sizeof( particle_t ) * GI->max_particles );
CL_ClearParticles ();
// this is used for EF_BRIGHTFIELD
for( i = 0; i < NUMVERTEXNORMALS; i++ )
{
cl_avelocities[i][0] = Com_RandomLong( 0, 255 ) * 0.01f;
cl_avelocities[i][1] = Com_RandomLong( 0, 255 ) * 0.01f;
cl_avelocities[i][2] = Com_RandomLong( 0, 255 ) * 0.01f;
}
tracerred = Cvar_Get( "tracerred", "0.8", 0, "tracer red component weight ( 0 - 1.0 )" );
tracergreen = Cvar_Get( "tracergreen", "0.8", 0, "tracer green component weight ( 0 - 1.0 )" );
tracerblue = Cvar_Get( "tracerblue", "0.4", 0, "tracer blue component weight ( 0 - 1.0 )" );
traceralpha = Cvar_Get( "traceralpha", "0.5", 0, "tracer alpha amount ( 0 - 1.0 )" );
tracerspeed = Cvar_Get( "tracerspeed", "6000", 0, "tracer speed" );
tracerlength = Cvar_Get( "tracerlength", "0.8", 0, "tracer length factor" );
traceroffset = Cvar_Get( "traceroffset", "30", 0, "tracer starting offset" );
}
/*
================
CL_ClearParticles
================
*/
void CL_ClearParticles( void )
{
int i;
if( !cl_particles ) return;
cl_free_particles = cl_particles;
cl_active_particles = NULL;
for( i = 0; i < GI->max_particles - 1; i++ )
cl_particles[i].next = &cl_particles[i+1];
cl_particles[GI->max_particles-1].next = NULL;
}
/*
================
CL_FreeParticles
================
*/
void CL_FreeParticles( void )
{
if( cl_particles ) Mem_Free( cl_particles );
cl_particles = NULL;
}
/*
================
CL_FreeParticle
move particle to freelist
================
*/
void CL_FreeParticle( particle_t *p )
{
if( p->type == pt_clientcustom && p->deathfunc )
{
// call right the deathfunc func before die
p->deathfunc( p );
}
p->next = cl_free_particles;
cl_free_particles = p;
}
/*
================
CL_AllocParticle
can return NULL if particles is out
================
*/
particle_t *CL_AllocParticle( void (*callback)( particle_t*, float ))
{
particle_t *p;
if( !cl_draw_particles->integer )
return NULL;
// never alloc particles when we not in game
if( !CL_IsInGame( )) return NULL;
if( !cl_free_particles )
{
MsgDev( D_INFO, "Overflow %d particles\n", GI->max_particles );
return NULL;
}
p = cl_free_particles;
cl_free_particles = p->next;
p->next = cl_active_particles;
cl_active_particles = p;
// clear old particle
p->type = pt_static;
VectorClear( p->vel );
VectorClear( p->org );
p->die = cl.time;
p->ramp = 0;
if( callback )
{
p->type = pt_clientcustom;
p->callback = callback;
}
return p;
}
static void CL_SparkTracerDraw( particle_t *p, float frametime )
{
float lifePerc = p->die - cl.time;
float grav = frametime * clgame.movevars.gravity * 0.05f;
float length, width;
int alpha = 255;
vec3_t delta;
VectorScale( p->vel, p->ramp, delta );
length = VectorLength( delta );
width = ( length < TRACER_WIDTH ) ? length : TRACER_WIDTH;
if( lifePerc < 0.5f ) alpha = (lifePerc * 2) * 255;
CL_DrawTracer( p->org, delta, width, clgame.palette[p->color], alpha, 0.0f, 0.8f );
p->vel[2] -= grav * 8; // use vox gravity
VectorMA( p->org, frametime, p->vel, p->org );
}
static void CL_TracerImplosion( particle_t *p, float frametime )
{
float lifePerc = p->die - cl.time;
float grav = frametime * clgame.movevars.gravity * 0.05f;
float length;
int alpha = 255;
vec3_t delta;
VectorScale( p->vel, p->ramp, delta );
length = VectorLength( delta );
if( lifePerc < 0.5f ) alpha = (lifePerc * 2) * 255;
p->vel[2] -= grav; // use slow gravity
CL_DrawTracer( p->org, delta, 1.5f, clgame.palette[p->color], alpha, 0.0f, 0.8f );
VectorMA( p->org, frametime, p->vel, p->org );
}
static void CL_BulletTracerDraw( particle_t *p, float frametime )
{
vec3_t lineDir, viewDir, cross;
vec3_t vecEnd, vecStart, vecDir;
float sDistance, eDistance, totalDist;
float dDistance, dTotal, fOffset;
int alpha = (int)(traceralpha->value * 255);
float width = 3.0f, life, frac, length;
vec3_t tmp;
// calculate distance
VectorCopy( p->vel, vecDir );
totalDist = VectorNormalizeLength( vecDir );
length = p->ramp; // ramp used as length
// calculate fraction
life = ( totalDist + length ) / ( max( 1.0f, tracerspeed->value ));
frac = life - ( p->die - cl.time ) + frametime;
// calculate our distance along our path
sDistance = tracerspeed->value * frac;
eDistance = sDistance - length;
// clip to start
sDistance = max( 0.0f, sDistance );
eDistance = max( 0.0f, eDistance );
if(( sDistance == 0.0f ) && ( eDistance == 0.0f ))
return;
// clip it
if( totalDist != 0.0f )
{
sDistance = min( sDistance, totalDist );
eDistance = min( eDistance, totalDist );
}
// get our delta to calculate the tc offset
dDistance = fabs( sDistance - eDistance );
dTotal = ( length != 0.0f ) ? length : 0.01f;
fOffset = ( dDistance / dTotal );
// find our points along our path
VectorMA( p->org, sDistance, vecDir, vecEnd );
VectorMA( p->org, eDistance, vecDir, vecStart );
// setup our info for drawing the line
VectorSubtract( vecEnd, vecStart, lineDir );
VectorSubtract( vecEnd, cl.refdef.vieworg, viewDir );
CrossProduct( lineDir, viewDir, cross );
VectorNormalize( cross );
GL_SetRenderMode( kRenderTransTexture );
GL_Bind( GL_TEXTURE0, cls.particleImage );
pglBegin( GL_QUADS );
pglColor4ub( clgame.palette[p->color][0], clgame.palette[p->color][1], clgame.palette[p->color][2], alpha );
VectorMA( vecStart, -width, cross, tmp );
pglTexCoord2f( 1.0f, 0.0f );
pglVertex3fv( tmp );
VectorMA( vecStart, width, cross, tmp );
pglTexCoord2f( 0.0f, 0.0f );
pglVertex3fv( tmp );
VectorMA( vecEnd, width, cross, tmp );
pglTexCoord2f( 0.0f, fOffset );
pglVertex3fv( tmp );
VectorMA( vecEnd, -width, cross, tmp );
pglTexCoord2f( 1.0f, fOffset );
pglVertex3fv( tmp );
pglEnd();
}
/*
================
CL_UpdateParticle
update particle color, position etc
================
*/
void CL_UpdateParticle( particle_t *p, float ft )
{
float time3 = 15.0 * ft;
float time2 = 10.0 * ft;
float time1 = 5.0 * ft;
float dvel = 4 * ft;
float grav = ft * clgame.movevars.gravity * 0.05f;
float size = 1.5f;
int i, iRamp, alpha = 255;
vec3_t right, up;
rgb_t color;
r_stats.c_particle_count++;
switch( p->type )
{
case pt_static:
break;
case pt_clientcustom:
if( p->callback )
{
p->callback( p, ft );
if( p->callback == CL_BulletTracerDraw )
return; // already drawed
else if( p->callback == CL_SparkTracerDraw )
return; // already drawed
else if( p->callback == CL_TracerImplosion )
return; // already drawed
}
break;
case pt_fire:
p->ramp += time1;
if( p->ramp >= 6 ) p->die = -1;
else p->color = ramp3[(int)p->ramp];
p->vel[2] += grav;
break;
case pt_explode:
p->ramp += time2;
if( p->ramp >= 8 ) p->die = -1;
else p->color = ramp1[(int)p->ramp];
for( i = 0; i < 3; i++ )
p->vel[i] += p->vel[i] * dvel;
p->vel[2] -= grav;
break;
case pt_explode2:
p->ramp += time3;
if( p->ramp >= 8 ) p->die = -1;
else p->color = ramp2[(int)p->ramp];
for( i = 0; i < 3; i++ )
p->vel[i] -= p->vel[i] * ft;
p->vel[2] -= grav;
break;
case pt_blob:
case pt_blob2:
p->ramp += time2;
iRamp = (int)p->ramp >> SIMSHIFT;
if( iRamp >= SPARK_COLORCOUNT )
{
p->ramp = 0.0f;
iRamp = 0;
}
p->color = CL_LookupColor( gSparkRamp[iRamp][0], gSparkRamp[iRamp][1], gSparkRamp[iRamp][2] );
for( i = 0; i < 2; i++ )
p->vel[i] -= p->vel[i] * 0.5f * ft;
p->vel[2] -= grav * 5.0f;
if( Com_RandomLong( 0, 3 ))
{
p->type = pt_blob;
alpha = 0;
}
else
{
p->type = pt_blob2;
alpha = 255;
}
break;
case pt_grav:
p->vel[2] -= grav * 20;
break;
case pt_slowgrav:
p->vel[2] = grav;
break;
case pt_vox_grav:
p->vel[2] -= grav * 8;
break;
case pt_vox_slowgrav:
p->vel[2] -= grav * 4;
break;
}
#if 0
// HACKHACK a scale up to keep particles from disappearing
size += (p->org[0] - cl.refdef.vieworg[0]) * cl.refdef.forward[0];
size += (p->org[1] - cl.refdef.vieworg[1]) * cl.refdef.forward[1];
size += (p->org[2] - cl.refdef.vieworg[2]) * cl.refdef.forward[2];
if( size < 20.0f ) size = 1.0f;
else size = 1.0f + size * 0.004f;
#endif
// scale the axes by radius
VectorScale( cl.refdef.right, size, right );
VectorScale( cl.refdef.up, size, up );
p->color = bound( 0, p->color, 255 );
VectorSet( color, clgame.palette[p->color][0], clgame.palette[p->color][1], clgame.palette[p->color][2] );
GL_SetRenderMode( kRenderTransTexture );
pglColor4ub( color[0], color[1], color[2], alpha );
GL_Bind( GL_TEXTURE0, cls.particleImage );
// add the 4 corner vertices.
pglBegin( GL_QUADS );
pglTexCoord2f( 0.0f, 1.0f );
pglVertex3f( p->org[0] - right[0] + up[0], p->org[1] - right[1] + up[1], p->org[2] - right[2] + up[2] );
pglTexCoord2f( 0.0f, 0.0f );
pglVertex3f( p->org[0] + right[0] + up[0], p->org[1] + right[1] + up[1], p->org[2] + right[2] + up[2] );
pglTexCoord2f( 1.0f, 0.0f );
pglVertex3f( p->org[0] + right[0] - up[0], p->org[1] + right[1] - up[1], p->org[2] + right[2] - up[2] );
pglTexCoord2f( 1.0f, 1.0f );
pglVertex3f( p->org[0] - right[0] - up[0], p->org[1] - right[1] - up[1], p->org[2] - right[2] - up[2] );
pglEnd();
if( p->type != pt_clientcustom )
{
// update position.
VectorMA( p->org, ft, p->vel, p->org );
}
}
void CL_DrawParticles( void )
{
particle_t *p, *kill;
float frametime;
if( !cl_draw_particles->integer || RI.refdef.onlyClientDraw )
return;
frametime = cl.time - cl.oldtime;
if( tracerred->modified || tracergreen->modified || tracerblue->modified )
{
gTracerColors[4][0] = (byte)(tracerred->value * 255);
gTracerColors[4][1] = (byte)(tracergreen->value * 255);
gTracerColors[4][2] = (byte)(tracerblue->value * 255);
tracerred->modified = tracergreen->modified = tracerblue->modified = false;
}
while( 1 )
{
// free time-expired particles
kill = cl_active_particles;
if( kill && kill->die < cl.time )
{
cl_active_particles = kill->next;
CL_FreeParticle( kill );
continue;
}
break;
}
for( p = cl_active_particles; p; p = p->next )
{
while( 1 )
{
kill = p->next;
if( kill && kill->die < cl.time )
{
p->next = kill->next;
CL_FreeParticle( kill );
continue;
}
break;
}
CL_UpdateParticle( p, frametime );
}
}
/*
===============
CL_EntityParticles
set EF_BRIGHTFIELD effect
===============
*/
void CL_EntityParticles( cl_entity_t *ent )
{
float angle;
float sr, sp, sy, cr, cp, cy;
vec3_t forward;
particle_t *p;
int i;
for( i = 0; i < NUMVERTEXNORMALS; i++ )
{
p = CL_AllocParticle( NULL );
if( !p ) return;
angle = cl.time * cl_avelocities[i][0];
SinCos( angle, &sy, &cy );
angle = cl.time * cl_avelocities[i][1];
SinCos( angle, &sp, &cp );
angle = cl.time * cl_avelocities[i][2];
SinCos( angle, &sr, &cr );
VectorSet( forward, cp * cy, cp * sy, -sp );
p->die += 0.01f;
p->color = 111; // yellow
p->type = pt_explode;
p->org[0] = ent->origin[0] + cl_avertexnormals[i][0] * 64.0f + forward[0] * 16.0f;
p->org[1] = ent->origin[1] + cl_avertexnormals[i][1] * 64.0f + forward[1] * 16.0f;
p->org[2] = ent->origin[2] + cl_avertexnormals[i][2] * 64.0f + forward[2] * 16.0f;
}
}
/*
===============
CL_ParticleExplosion
===============
*/
void CL_ParticleExplosion( const vec3_t org )
{
particle_t *p;
int i, j;
int hSound;
if( !org ) return;
hSound = S_RegisterSound( "weapons/explode3.wav" );
S_StartSound( org, 0, CHAN_AUTO, hSound, VOL_NORM, ATTN_NORM, PITCH_NORM, 0 );
for( i = 0; i < 1024; i++ )
{
p = CL_AllocParticle( NULL );
if( !p ) return;
p->die += 5.0f;
p->color = ramp1[0];
p->ramp = Com_RandomLong( 0, 4 );
if( i & 1 )
{
p->type = pt_explode;
for( j = 0; j < 3; j++ )
{
p->org[j] = org[j] + Com_RandomFloat( -16.0f, 16.0f );
p->vel[j] = Com_RandomFloat( -256.0f, 256.0f );
}
}
else
{
p->type = pt_explode2;
for( j = 0; j < 3; j++ )
{
p->org[j] = org[j] + Com_RandomFloat( -16.0f, 16.0f );
p->vel[j] = Com_RandomFloat( -256.0f, 256.0f );
}
}
}
}
/*
===============
CL_ParticleExplosion2
===============
*/
void CL_ParticleExplosion2( const vec3_t org, int colorStart, int colorLength )
{
int i, j;
int colorMod = 0;
particle_t *p;
int hSound;
if( !org ) return;
hSound = S_RegisterSound( "weapons/explode3.wav" );
S_StartSound( org, 0, CHAN_AUTO, hSound, VOL_NORM, ATTN_NORM, PITCH_NORM, 0 );
for( i = 0; i < 512; i++ )
{
p = CL_AllocParticle( NULL );
if( !p ) return;
p->die += 0.3f;
p->color = colorStart + ( colorMod % colorLength );
colorMod++;
p->type = pt_blob;
for( j = 0; j < 3; j++ )
{
p->org[j] = org[j] + Com_RandomFloat( -16.0f, 16.0f );
p->vel[j] = Com_RandomFloat( -256.0f, 256.0f );
}
}
}
/*
===============
CL_BlobExplosion
===============
*/
void CL_BlobExplosion( const vec3_t org )
{
particle_t *p;
int i, j;
int hSound;
if( !org ) return;
hSound = S_RegisterSound( "weapons/explode3.wav" );
S_StartSound( org, 0, CHAN_AUTO, hSound, VOL_NORM, ATTN_NORM, PITCH_NORM, 0 );
for( i = 0; i < 1024; i++ )
{
p = CL_AllocParticle( NULL );
if( !p ) return;
p->die += 1.0f + Com_RandomFloat( 0, 0.4f );
if( i & 1 )
{
p->type = pt_explode;
p->color = 66 + rand() % 6;
for( j = 0; j < 3; j++ )
{
p->org[j] = org[j] + Com_RandomFloat( -16.0f, 16.0f );
p->vel[j] = Com_RandomFloat( -256.0f, 256.0f );
}
}
else
{
p->type = pt_explode2;
p->color = 150 + rand() % 6;
for( j = 0; j < 3; j++ )
{
p->org[j] = org[j] + Com_RandomFloat( -16.0f, 16.0f );
p->vel[j] = Com_RandomFloat( -256.0f, 256.0f );
}
}
}
}
/*
===============
ParticleEffect
PARTICLE_EFFECT on server
===============
*/
void CL_RunParticleEffect( const vec3_t org, const vec3_t dir, int color, int count )
{
particle_t *p;
int i, j;
if( count == 1024 )
{
// Quake hack: count == 255 it's a RocketExplode
CL_ParticleExplosion( org );
return;
}
for( i = 0; i < count; i++ )
{
p = CL_AllocParticle( NULL );
if( !p ) return;
p->die += Com_RandomFloat( 0.1f, 0.5f );
p->color = ( color & ~7 ) + Com_RandomLong( 0, 8 );
p->type = pt_slowgrav;
for( j = 0; j < 3; j++ )
{
p->org[j] = org[j] + Com_RandomFloat( -16, 16 );
p->vel[j] = dir[j] * 15;
}
}
}
/*
===============
CL_Blood
particle spray
===============
*/
void CL_Blood( const vec3_t org, const vec3_t dir, int pcolor, int speed )
{
particle_t *p;
int i, j;
for( i = 0; i < speed * 20; i++ )
{
p = CL_AllocParticle( NULL );
if( !p ) return;
p->die += Com_RandomFloat( 0.1f, 0.5f );
p->type = pt_slowgrav;
p->color = pcolor;
for( j = 0; j < 3; j++ )
{
p->org[j] = org[j] + Com_RandomFloat( -16.0f, 16.0f );
p->vel[j] = dir[j] * speed;
}
}
}
/*
===============
CL_BloodStream
particle spray 2
===============
*/
void CL_BloodStream( const vec3_t org, const vec3_t dir, int pcolor, int speed )
{
particle_t *p;
int i, j;
for( i = 0; i < speed * 20; i++ )
{
p = CL_AllocParticle( NULL );
if( !p ) return;
p->die += Com_RandomFloat( 0.2f, 0.8f );
p->type = pt_vox_grav;
p->color = pcolor;
for( j = 0; j < 3; j++ )
{
p->org[j] = org[j];
p->vel[j] = dir[j] * speed;
}
}
}
/*
===============
CL_LavaSplash
===============
*/
void CL_LavaSplash( const vec3_t org )
{
particle_t *p;
float vel;
vec3_t dir;
int i, j, k;
for( i = -16; i < 16; i++ )
{
for( j = -16; j <16; j++ )
{
for( k = 0; k < 1; k++ )
{
p = CL_AllocParticle( NULL );
if( !p ) return;
p->die += 2.0f + Com_RandomFloat( 0.0f, 0.65f );
p->color = 224 + Com_RandomLong( 0, 8 );
p->type = pt_slowgrav;
dir[0] = j * 8 + Com_RandomLong( 0, 8 );
dir[1] = i * 8 + Com_RandomLong( 0, 8 );
dir[2] = 256;
p->org[0] = org[0] + dir[0];
p->org[1] = org[1] + dir[1];
p->org[2] = org[2] + Com_RandomLong( 0, 64 );
VectorNormalize( dir );
vel = 50 + Com_RandomLong( 0, 64 );
VectorScale( dir, vel, p->vel );
}
}
}
}
/*
===============
CL_ParticleBurst
===============
*/
void CL_ParticleBurst( const vec3_t org, int size, int color, float life )
{
particle_t *p;
float vel;
vec3_t dir;
int i, j, k;
for( i = -size; i < size; i++ )
{
for( j = -size; j < size; j++ )
{
for( k = 0; k < 1; k++ )
{
p = CL_AllocParticle( NULL );
if( !p ) return;
p->die += life + Com_RandomFloat( 0.0f, 0.1f );
p->color = color;
p->type = pt_slowgrav;
dir[0] = j * 8 + Com_RandomLong( 0, 8 );
dir[1] = i * 8 + Com_RandomLong( 0, 8 );
dir[2] = 256;
p->org[0] = org[0] + dir[0];
p->org[1] = org[1] + dir[1];
p->org[2] = org[2] + Com_RandomLong( 0, 64 );
VectorNormalize( dir );
vel = 50 + Com_RandomLong( 0, 64 );
VectorScale( dir, vel, p->vel );
}
}
}
}
/*
===============
CL_TeleportSplash
===============
*/
void CL_TeleportSplash( const vec3_t org )
{
particle_t *p;
vec3_t dir;
int i, j, k;
for( i = -16; i < 16; i += 4 )
{
for( j = -16; j < 16; j += 4 )
{
for( k = -24; k < 32; k += 4 )
{
p = CL_AllocParticle( NULL );
if( !p ) return;
p->die += Com_RandomFloat( 0.2f, 0.36f );
p->color = Com_RandomLong( 7, 14 );
p->type = pt_slowgrav;
dir[0] = j * 8;
dir[1] = i * 8;
dir[2] = k * 8;
p->org[0] = org[0] + i + Com_RandomFloat( -4.0f, 4.0f );
p->org[1] = org[1] + j + Com_RandomFloat( -4.0f, 4.0f );
p->org[2] = org[2] + k + Com_RandomFloat( -4.0f, 4.0f );
VectorNormalize( dir );
VectorScale( dir, Com_RandomLong( 50, 114 ), p->vel );
}
}
}
}
/*
===============
CL_RocketTrail
===============
*/
void CL_RocketTrail( vec3_t start, vec3_t end, int type )
{
vec3_t vec;
float len;
particle_t *p;
int j, dec;
static int tracercount;
VectorSubtract( end, start, vec );
len = VectorNormalizeLength( vec );
if( type < 128 )
{
dec = 3;
}
else
{
dec = 1;
type -= 128;
}
while( len > 0 )
{
len -= dec;
p = CL_AllocParticle( NULL );
if( !p ) return;
p->die += 2.0f;
switch( type )
{
case 0: // rocket trail
p->ramp = Com_RandomLong( 0, 4 );
p->color = ramp3[(int)p->ramp];
p->type = pt_fire;
for( j = 0; j < 3; j++ )
p->org[j] = start[j] + ((rand() % 6 ) - 3 );
break;
case 1: // smoke smoke
p->ramp = Com_RandomLong( 2, 6 );
p->color = ramp3[(int)p->ramp];
p->type = pt_fire;
for( j = 0; j < 3; j++ )
p->org[j] = start[j] + ((rand() % 6 ) - 3 );
break;
case 2: // blood
p->type = pt_grav;
p->color = Com_RandomLong( 67, 71 );
for( j = 0; j < 3; j++ )
p->org[j] = start[j] + ((rand() % 6 ) - 3 );
break;
case 3:
case 5: // tracer
p->die += 0.5f;
p->type = pt_static;
if( type == 3 ) p->color = 52 + (( tracercount & 4 )<<1 );
else p->color = 230 + (( tracercount & 4 )<<1 );
tracercount++;
VectorCopy( start, p->org );
if( tracercount & 1 )
{
p->vel[0] = 30 * vec[1];
p->vel[1] = 30 * -vec[0];
}
else
{
p->vel[0] = 30 * -vec[1];
p->vel[1] = 30 * vec[0];
}
break;
case 4: // slight blood
p->type = pt_grav;
p->color = Com_RandomLong( 67, 71 );
for( j = 0; j < 3; j++ )
p->org[j] = start[j] + Com_RandomFloat( -3.0f, 3.0f );
len -= 3;
break;
case 6: // voor trail
p->color = Com_RandomLong( 152, 156 );
p->type = pt_static;
p->die += 0.3f;
for( j = 0; j < 3; j++ )
p->org[j] = start[j] + Com_RandomFloat( -16.0f, 16.0f );
break;
}
VectorAdd( start, vec, start );
}
}
/*
================
CL_DrawLine
================
*/
static void CL_DrawLine( const vec3_t start, const vec3_t end, int pcolor, float life, float gap )
{
particle_t *p;
float len, curdist;
vec3_t diff;
int i;
// Determine distance;
VectorSubtract( end, start, diff );
len = VectorNormalizeLength( diff );
curdist = 0;
while( curdist <= len )
{
p = CL_AllocParticle( NULL );
if( !p ) return;
for( i = 0; i < 3; i++ )
p->org[i] = start[i] + curdist * diff[i];
p->color = pcolor;
p->type = pt_static;
p->die += life;
curdist += gap;
}
}
/*
================
CL_DrawRectangle
================
*/
void CL_DrawRectangle( const vec3_t tl, const vec3_t bl, const vec3_t tr, const vec3_t br, int pcolor, float life )
{
CL_DrawLine( tl, bl, pcolor, life, 2.0f );
CL_DrawLine( bl, br, pcolor, life, 2.0f );
CL_DrawLine( br, tr, pcolor, life, 2.0f );
CL_DrawLine( tr, tl, pcolor, life, 2.0f );
}
void CL_ParticleLine( const vec3_t start, const vec3_t end, byte r, byte g, byte b, float life )
{
int pcolor;
pcolor = CL_LookupColor( r, g, b );
CL_DrawLine( start, end, pcolor, life, 2.0f );
}
/*
================
CL_ParticleBox
================
*/
void CL_ParticleBox( const vec3_t mins, const vec3_t maxs, byte r, byte g, byte b, float life )
{
vec3_t tmp, p[8];
int i, col;
col = CL_LookupColor( r, g, b );
for( i = 0; i < 8; i++ )
{
tmp[0] = (i & 1) ? mins[0] : maxs[0];
tmp[1] = (i & 2) ? mins[1] : maxs[1];
tmp[2] = (i & 4) ? mins[2] : maxs[2];
VectorCopy( tmp, p[i] );
}
for( i = 0; i < 6; i++ )
{
CL_DrawRectangle( p[boxpnt[i][1]], p[boxpnt[i][0]], p[boxpnt[i][2]], p[boxpnt[i][3]], col, life );
}
}
/*
================
CL_ShowLine
================
*/
void CL_ShowLine( const vec3_t start, const vec3_t end )
{
int pcolor;
pcolor = CL_LookupColor( 192, 0, 0 );
CL_DrawLine( start, end, pcolor, 30.0f, 5.0f );
}
/*
===============
CL_BulletImpactParticles
===============
*/
void CL_BulletImpactParticles( const vec3_t org )
{
particle_t *p;
vec3_t pos, dir;
float vel;
int i, j;
// do sparks
// randomize position
pos[0] = org[0] + Com_RandomFloat( -2.0f, 2.0f );
pos[1] = org[1] + Com_RandomFloat( -2.0f, 2.0f );
pos[2] = org[2] + Com_RandomFloat( -2.0f, 2.0f );
// create a 8 random spakle tracers
for( i = 0; i < 8; i++ )
{
dir[0] = Com_RandomFloat( -1.0f, 1.0f );
dir[1] = Com_RandomFloat( -1.0f, 1.0f );
dir[2] = Com_RandomFloat( -1.0f, 1.0f );
vel = Com_RandomFloat( SPARK_ELECTRIC_MINSPEED, SPARK_ELECTRIC_MAXSPEED );
CL_SparkleTracer( pos, dir, vel );
}
for( i = 0; i < 12; i++ )
{
p = CL_AllocParticle( NULL );
if( !p ) return;
p->die += 1.0f;
p->color = 0; // black
p->type = pt_grav;
for( j = 0; j < 3; j++ )
{
p->org[j] = org[j] + Com_RandomFloat( -2.0f, 3.0f );
p->vel[j] = Com_RandomFloat( -70.0f, 70.0f );
}
}
}
/*
===============
CL_FlickerParticles
===============
*/
void CL_FlickerParticles( const vec3_t org )
{
particle_t *p;
int i, j;
for( i = 0; i < 16; i++ )
{
p = CL_AllocParticle( NULL );
if( !p ) return;
p->die += Com_RandomFloat( 0.5f, 2.0f );
p->type = pt_blob;
for( j = 0; j < 3; j++ )
p->org[j] = org[j] + Com_RandomFloat( -32.0f, 32.0f );
p->vel[2] = Com_RandomFloat( 64.0f, 100.0f );
}
}
/*
===============
CL_DebugParticle
just for debug purposes
===============
*/
void CL_DebugParticle( const vec3_t pos, byte r, byte g, byte b )
{
particle_t *p;
p = CL_AllocParticle( NULL );
if( !p ) return;
VectorCopy( pos, p->org );
p->die += 10.0f;
p->color = CL_LookupColor( r, g, b );
}
/*
==============================================================
TRACERS MANAGEMENT (particle extension)
==============================================================
*/
/*
================
CL_CullTracer
check tracer bbox
================
*/
static qboolean CL_CullTracer( const vec3_t start, const vec3_t end )
{
vec3_t mins, maxs;
int i;
// compute the bounding box
for( i = 0; i < 3; i++ )
{
if( start[i] < end[i] )
{
mins[i] = start[i];
maxs[i] = end[i];
}
else
{
mins[i] = end[i];
maxs[i] = start[i];
}
// don't let it be zero sized
if( mins[i] == maxs[i] )
{
maxs[i] += 1;
}
}
// check bbox
return R_CullBox( mins, maxs, RI.clipFlags );
}
/*
================
CL_TracerComputeVerts
compute four vertexes in screen space
================
*/
qboolean CL_TracerComputeVerts( const vec3_t start, const vec3_t delta, float width, vec3_t *pVerts )
{
vec3_t end, screenStart, screenEnd;
vec3_t tmp, tmp2, normal;
VectorAdd( start, delta, end );
// clip the tracer
if( CL_CullTracer( start, end ))
return false;
// transform point into the screen space
TriWorldToScreen( (float *)start, screenStart );
TriWorldToScreen( (float *)end, screenEnd );
VectorSubtract( screenStart, screenEnd, tmp );
tmp[2] = 0; // we don't need Z, we're in screen space
VectorNormalize( tmp );
// build point along noraml line (normal is -y, x)
VectorScale( cl.refdef.up, tmp[0], normal );
VectorScale( cl.refdef.right, -tmp[1], tmp2 );
VectorSubtract( normal, tmp2, normal );
// compute four vertexes
VectorScale( normal, width, tmp );
VectorSubtract( start, tmp, pVerts[0] );
VectorAdd( start, tmp, pVerts[1] );
VectorAdd( pVerts[0], delta, pVerts[2] );
VectorAdd( pVerts[1], delta, pVerts[3] );
return true;
}
/*
================
CL_DrawTracer
draws a single tracer
================
*/
void CL_DrawTracer( vec3_t start, vec3_t delta, float width, rgb_t color, int alpha, float startV, float endV )
{
// Clip the tracer
vec3_t verts[4];
if ( !CL_TracerComputeVerts( start, delta, width, verts ))
return;
// NOTE: Gotta get the winding right so it's not backface culled
// (we need to turn of backface culling for these bad boys)
GL_SetRenderMode( kRenderTransTexture );
pglColor4ub( color[0], color[1], color[2], alpha );
GL_Bind( GL_TEXTURE0, cls.particleImage );
pglBegin( GL_QUADS );
pglTexCoord2f( 0.0f, endV );
pglVertex3fv( verts[2] );
pglTexCoord2f( 1.0f, endV );
pglVertex3fv( verts[3] );
pglTexCoord2f( 1.0f, startV );
pglVertex3fv( verts[1] );
pglTexCoord2f( 0.0f, startV );
pglVertex3fv( verts[0] );
pglEnd();
}
/*
===============
CL_SparkleTracer
===============
*/
void CL_SparkleTracer( const vec3_t pos, const vec3_t dir, float vel )
{
particle_t *p;
byte *color;
p = CL_AllocParticle( CL_SparkTracerDraw );
if( !p ) return;
color = gTracerColors[5]; // Yellow-Orange
VectorCopy( pos, p->org );
p->die += Com_RandomFloat( 0.45f, 0.7f );
p->color = CL_LookupColor( color[0], color[1], color[2] );
p->ramp = Com_RandomFloat( 0.07f, 0.08f ); // ramp used as tracer length
VectorScale( dir, vel, p->vel );
}
/*
===============
CL_StreakTracer
===============
*/
void CL_StreakTracer( const vec3_t pos, const vec3_t velocity, int colorIndex )
{
particle_t *p;
byte *color;
p = CL_AllocParticle( CL_SparkTracerDraw );
if( !p ) return;
if( colorIndex > ( sizeof( gTracerColors ) / sizeof( gTracerColors[0] )))
{
p->color = bound( 0, colorIndex, 255 );
}
else
{
color = gTracerColors[colorIndex];
p->color = CL_LookupColor( color[0], color[1], color[2] );
}
p->die += Com_RandomFloat( 0.5f, 1.0f );
VectorCopy( velocity, p->vel );
VectorCopy( pos, p->org );
p->ramp = Com_RandomFloat( 0.05f, 0.08f );
}
/*
===============
CL_TracerEffect
===============
*/
void CL_TracerEffect( const vec3_t start, const vec3_t end )
{
particle_t *p;
byte *color;
vec3_t dir;
float life, dist;
p = CL_AllocParticle( CL_BulletTracerDraw );
if( !p ) return;
// get out shot direction and length
VectorSubtract( end, start, dir );
VectorCopy( dir, p->vel );
dist = VectorNormalizeLength( dir );
// don't make small tracers
if( dist <= traceroffset->value )
return;
p->ramp = Com_RandomFloat( 200.0f, 256.0f ) * tracerlength->value;
color = gTracerColors[4];
life = ( dist + p->ramp ) / ( max( 1.0f, tracerspeed->value ));
p->color = CL_LookupColor( color[0], color[1], color[2] );
VectorCopy( start, p->org );
p->die += life;
}
/*
===============
CL_TracerEffect
===============
*/
void CL_UserTracerParticle( float *org, float *vel, float life, int colorIndex, float length, byte deathcontext,
void (*deathfunc)( particle_t *p ))
{
particle_t *p;
byte *color;
p = CL_AllocParticle( CL_BulletTracerDraw );
if( !p ) return;
if( colorIndex > ( sizeof( gTracerColors ) / sizeof( gTracerColors[0] )))
{
p->color = bound( 0, colorIndex, 255 );
}
else
{
color = gTracerColors[colorIndex];
p->color = CL_LookupColor( color[0], color[1], color[2] );
}
VectorCopy( org, p->org );
VectorCopy( vel, p->vel );
p->ramp = length; // ramp used as length
p->context = deathcontext;
p->deathfunc = deathfunc;
p->die += life;
}
/*
===============
CL_TracerParticles
allow more customization
===============
*/
particle_t *CL_TracerParticles( float *org, float *vel, float life )
{
particle_t *p;
byte *color;
p = CL_AllocParticle( CL_BulletTracerDraw );
if( !p ) return NULL;
p->ramp = Com_RandomFloat( 200.0f, 256.0f ) * tracerlength->value;
color = gTracerColors[4];
p->color = CL_LookupColor( color[0], color[1], color[2] );
VectorCopy( org, p->org );
VectorCopy( vel, p->vel );
p->die += life;
return p;
}
/*
===============
CL_SparkShower
Creates 8 random tracers
===============
*/
void CL_SparkShower( const vec3_t org )
{
vec3_t pos, dir;
model_t *pmodel;
float vel;
int i;
// randomize position
pos[0] = org[0] + Com_RandomFloat( -2.0f, 2.0f );
pos[1] = org[1] + Com_RandomFloat( -2.0f, 2.0f );
pos[2] = org[2] + Com_RandomFloat( -2.0f, 2.0f );
pmodel = Mod_Handle( CL_FindModelIndex( "sprites/richo1.spr" ));
CL_RicochetSprite( pos, pmodel, 0.0f, Com_RandomFloat( 0.4, 0.6f ));
// create a 8 random spakle tracers
for( i = 0; i < 8; i++ )
{
dir[0] = Com_RandomFloat( -1.0f, 1.0f );
dir[1] = Com_RandomFloat( -1.0f, 1.0f );
dir[2] = Com_RandomFloat( -1.0f, 1.0f );
vel = Com_RandomFloat( SPARK_ELECTRIC_MINSPEED, SPARK_ELECTRIC_MAXSPEED );
CL_SparkleTracer( pos, dir, vel );
}
}
/*
===============
CL_Implosion
===============
*/
void CL_Implosion( const vec3_t end, float radius, int count, float life )
{
particle_t *p;
float vel;
vec3_t dir, m_vecPos;
int i, colorIndex;
colorIndex = CL_LookupColor( gTracerColors[5][0], gTracerColors[5][1], gTracerColors[5][2] );
for( i = 0; i < count; i++ )
{
p = CL_AllocParticle( CL_TracerImplosion );
if( !p ) return;
dir[0] = Com_RandomFloat( -1.0f, 1.0f );
dir[1] = Com_RandomFloat( -1.0f, 1.0f );
dir[2] = Com_RandomFloat( -1.0f, 1.0f );
VectorNormalize( dir );
VectorMA( end, -radius, dir, m_vecPos );
// velocity based on how far particle has to travel away from org
vel = Com_RandomFloat( radius * 0.5f, radius * 1.5f );
VectorCopy( m_vecPos, p->org );
p->color = colorIndex;
p->ramp = 0.15f; // length based on velocity
VectorScale( dir, vel, p->vel );
// die right when you get there
p->die += ( life != 0.0f ) ? life : ( radius / vel );
}
}
/*
===============
CL_ReadPointFile_f
===============
*/
void CL_ReadPointFile_f( void )
{
char *afile, *pfile;
vec3_t org;
int count;
particle_t *p;
char filename[64];
string token;
Q_snprintf( filename, sizeof( filename ), "maps/%s.pts", clgame.mapname );
afile = FS_LoadFile( filename, NULL, false );
if( !afile )
{
MsgDev( D_ERROR, "couldn't open %s\n", filename );
return;
}
Msg( "Reading %s...\n", filename );
count = 0;
pfile = afile;
while( 1 )
{
pfile = COM_ParseFile( pfile, token );
if( !pfile ) break;
org[0] = Q_atof( token );
pfile = COM_ParseFile( pfile, token );
if( !pfile ) break;
org[1] = Q_atof( token );
pfile = COM_ParseFile( pfile, token );
if( !pfile ) break;
org[2] = Q_atof( token );
count++;
if( !cl_free_particles )
{
MsgDev( D_ERROR, "CL_ReadPointFile: not enough free particles!\n" );
break;
}
// NOTE: can't use CL_AllocateParticles because running from the console
p = cl_free_particles;
cl_free_particles = p->next;
p->next = cl_active_particles;
cl_active_particles = p;
p->ramp = 0;
p->die = 99999;
p->color = (-count) & 15;
p->type = pt_static;
VectorClear( p->vel );
VectorCopy( org, p->org );
}
Mem_Free( afile );
if( count ) Msg( "%i points read\n", count );
else Msg( "map %s has no leaks!\n", clgame.mapname );
}