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_beams.c

2031 lines
50 KiB
C

/*
gl_beams.c - beams rendering
Copyright (C) 2009 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 "r_efx.h"
#include "event_flags.h"
#include "entity_types.h"
#include "triangleapi.h"
#include "customentity.h"
#include "cl_tent.h"
#include "pm_local.h"
#include "gl_local.h"
#include "studio.h"
#define NOISE_DIVISIONS 64 // don't touch - many tripmines cause the crash when it equal 128
typedef struct
{
vec3_t pos;
float texcoord; // Y texture coordinate
float width;
} beamseg_t;
/*
==============================================================
FRACTAL NOISE
==============================================================
*/
static float rgNoise[NOISE_DIVISIONS+1]; // global noise array
// freq2 += step * 0.1;
// Fractal noise generator, power of 2 wavelength
static void FracNoise( float *noise, int divs )
{
int div2;
div2 = divs >> 1;
if( divs < 2 ) return;
// noise is normalized to +/- scale
noise[div2] = ( noise[0] + noise[divs] ) * 0.5f + divs * COM_RandomFloat( -0.125f, 0.125f );
if( div2 > 1 )
{
FracNoise( &noise[div2], div2 );
FracNoise( noise, div2 );
}
}
static void SineNoise( float *noise, int divs )
{
float freq = 0;
float step = M_PI / (float)divs;
int i;
for( i = 0; i < divs; i++ )
{
noise[i] = sin( freq );
freq += step;
}
}
/*
==============================================================
BEAM MATHLIB
==============================================================
*/
static void R_BeamComputePerpendicular( const vec3_t vecBeamDelta, vec3_t pPerp )
{
// direction in worldspace of the center of the beam
vec3_t vecBeamCenter;
VectorNormalize2( vecBeamDelta, vecBeamCenter );
CrossProduct( RI.vforward, vecBeamCenter, pPerp );
VectorNormalize( pPerp );
}
static void R_BeamComputeNormal( const vec3_t vStartPos, const vec3_t vNextPos, vec3_t pNormal )
{
// vTangentY = line vector for beam
vec3_t vTangentY, vDirToBeam;
VectorSubtract( vStartPos, vNextPos, vTangentY );
// vDirToBeam = vector from viewer origin to beam
VectorSubtract( vStartPos, RI.vieworg, vDirToBeam );
// get a vector that is perpendicular to us and perpendicular to the beam.
// this is used to fatten the beam.
CrossProduct( vTangentY, vDirToBeam, pNormal );
VectorNormalizeFast( pNormal );
}
/*
==============================================================
BEAM ALLOCATE & PROCESSING
==============================================================
*/
/*
==============
R_BeamAlloc
==============
*/
BEAM *R_BeamAlloc( void )
{
BEAM *pBeam;
if( !cl_free_beams )
return NULL;
pBeam = cl_free_beams;
cl_free_beams = pBeam->next;
memset( pBeam, 0, sizeof( *pBeam ));
pBeam->next = cl_active_beams;
cl_active_beams = pBeam;
pBeam->die = cl.time;
return pBeam;
}
/*
==============
R_BeamFree
==============
*/
void R_BeamFree( BEAM *pBeam )
{
// free particles that have died off.
R_FreeDeadParticles( &pBeam->particles );
// now link into free list;
pBeam->next = cl_free_beams;
cl_free_beams = pBeam;
}
/*
==============
R_BeamSetup
generic function. all beams must be
passed through this
==============
*/
void R_BeamSetup( BEAM *pbeam, vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed )
{
model_t *sprite = Mod_Handle( modelIndex );
if( !sprite ) return;
pbeam->type = BEAM_POINTS;
pbeam->modelIndex = modelIndex;
pbeam->frame = 0;
pbeam->frameRate = 0;
pbeam->frameCount = Mod_FrameCount( sprite );
VectorCopy( start, pbeam->source );
VectorCopy( end, pbeam->target );
VectorSubtract( end, start, pbeam->delta );
pbeam->freq = speed * cl.time;
pbeam->die = life + cl.time;
pbeam->amplitude = amplitude;
pbeam->brightness = brightness;
pbeam->width = width;
pbeam->speed = speed;
if( amplitude >= 0.50f )
pbeam->segments = VectorLength( pbeam->delta ) * 0.25f + 3.0f; // one per 4 pixels
else pbeam->segments = VectorLength( pbeam->delta ) * 0.075f + 3.0f; // one per 16 pixels
pbeam->pFollowModel = NULL;
pbeam->flags = 0;
}
/*
==============
R_BeamSetAttributes
set beam attributes
==============
*/
void R_BeamSetAttributes( BEAM *pbeam, float r, float g, float b, float framerate, int startFrame )
{
pbeam->frame = (float)startFrame;
pbeam->frameRate = framerate;
pbeam->r = r;
pbeam->g = g;
pbeam->b = b;
}
/*
==============
R_BeamLightning
template for new beams
==============
*/
BEAM *R_BeamLightning( vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed )
{
BEAM *pbeam = R_BeamAlloc();
if( !pbeam ) return NULL;
pbeam->die = cl.time;
if( modelIndex < 0 )
return NULL;
R_BeamSetup( pbeam, start, end, modelIndex, life, width, amplitude, brightness, speed );
return pbeam;
}
/*
==============
R_BeamGetEntity
extract entity number from index
handle user entities
==============
*/
static cl_entity_t *R_BeamGetEntity( int index )
{
if( index < 0 )
return clgame.dllFuncs.pfnGetUserEntity( BEAMENT_ENTITY( -index ));
return CL_GetEntityByIndex( BEAMENT_ENTITY( index ));
}
/*
==============
R_BeamComputePoint
compute attachment point for beam
==============
*/
static qboolean R_BeamComputePoint( int beamEnt, vec3_t pt )
{
cl_entity_t *ent;
int attach;
ent = R_BeamGetEntity( beamEnt );
if( beamEnt < 0 )
attach = BEAMENT_ATTACHMENT( -beamEnt );
else attach = BEAMENT_ATTACHMENT( beamEnt );
if( !ent )
{
MsgDev( D_ERROR, "R_BeamComputePoint: invalid entity %i\n", BEAMENT_ENTITY( beamEnt ));
VectorClear( pt );
return false;
}
// get attachment
if( attach > 0 )
VectorCopy( ent->attachment[attach - 1], pt );
else if(( ent->index - 1 ) == cl.playernum )
VectorCopy( cl.simorg, pt );
else VectorCopy( ent->origin, pt );
return true;
}
/*
==============
R_BeamRecomputeEndpoints
Recomputes beam endpoints..
==============
*/
qboolean R_BeamRecomputeEndpoints( BEAM *pbeam )
{
if( FBitSet( pbeam->flags, FBEAM_STARTENTITY ))
{
cl_entity_t *start = R_BeamGetEntity( pbeam->startEntity );
if( R_BeamComputePoint( pbeam->startEntity, pbeam->source ))
{
if( !pbeam->pFollowModel )
pbeam->pFollowModel = start->model;
SetBits( pbeam->flags, FBEAM_STARTVISIBLE );
}
else if( !FBitSet( pbeam->flags, FBEAM_FOREVER ))
{
ClearBits( pbeam->flags, FBEAM_STARTENTITY );
}
}
if( FBitSet( pbeam->flags, FBEAM_ENDENTITY ))
{
cl_entity_t *end = R_BeamGetEntity( pbeam->endEntity );
if( R_BeamComputePoint( pbeam->endEntity, pbeam->target ))
{
if( !pbeam->pFollowModel )
pbeam->pFollowModel = end->model;
SetBits( pbeam->flags, FBEAM_ENDVISIBLE );
}
else if( !FBitSet( pbeam->flags, FBEAM_FOREVER ))
{
ClearBits( pbeam->flags, FBEAM_ENDENTITY );
pbeam->die = cl.time;
return false;
}
else
{
return false;
}
}
if( FBitSet( pbeam->flags, FBEAM_STARTENTITY ) && !FBitSet( pbeam->flags, FBEAM_STARTVISIBLE ))
return false;
return true;
}
/*
==============
R_BeamCull
Cull the beam by bbox
==============
*/
qboolean R_BeamCull( const vec3_t start, const vec3_t end, qboolean pvsOnly )
{
vec3_t mins, maxs;
int i;
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.0f;
}
// check bbox
if( Mod_BoxVisible( mins, maxs, Mod_GetCurrentVis( )))
{
if( pvsOnly || !R_CullBox( mins, maxs ))
{
// beam is visible
return false;
}
}
// beam is culled
return true;
}
/*
==============================================================
BEAM DRAW METHODS
==============================================================
*/
/*
================
R_DrawSegs
general code for drawing beams
================
*/
static void R_DrawSegs( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments, int flags )
{
int noiseIndex, noiseStep;
int i, total_segs, segs_drawn;
float div, length, fraction, factor;
float flMaxWidth, vLast, vStep, brightness;
vec3_t perp1, vLastNormal;
beamseg_t curSeg;
if( segments < 2 ) return;
length = VectorLength( delta );
flMaxWidth = width * 0.5f;
div = 1.0f / ( segments - 1 );
if( length * div < flMaxWidth * 1.414f )
{
// here, we have too many segments; we could get overlap... so lets have less segments
segments = (int)( length / ( flMaxWidth * 1.414f )) + 1.0f;
if( segments < 2 ) segments = 2;
}
if( segments > NOISE_DIVISIONS )
segments = NOISE_DIVISIONS;
div = 1.0f / (segments - 1);
length *= 0.01f;
// UNDONE: Expose texture length scale factor to control "fuzziness"
vStep = length * div; // Texture length texels per space pixel
// Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture)
vLast = fmod( freq * speed, 1 );
if( flags & FBEAM_SINENOISE )
{
if( segments < 16 )
{
segments = 16;
div = 1.0f / ( segments - 1 );
}
scale *= 100.0f;
length = segments * 0.1f;
}
else
{
scale *= length;
}
// Iterator to resample noise waveform (it needs to be generated in powers of 2)
noiseStep = (int)((float)( NOISE_DIVISIONS - 1 ) * div * 65536.0f );
noiseIndex = 0;
if( FBitSet( flags, FBEAM_SINENOISE ))
{
noiseIndex = 0;
}
brightness = 1.0f;
if( FBitSet( flags, FBEAM_SHADEIN ))
{
brightness = 0;
}
// Choose two vectors that are perpendicular to the beam
R_BeamComputePerpendicular( delta, perp1 );
total_segs = segments;
segs_drawn = 0;
// specify all the segments.
for( i = 0; i < segments; i++ )
{
beamseg_t nextSeg;
vec3_t vPoint1, vPoint2;
Assert( noiseIndex < ( NOISE_DIVISIONS << 16 ));
fraction = i * div;
if( FBitSet( flags, FBEAM_SHADEIN ) && FBitSet( flags, FBEAM_SHADEOUT ))
{
if( fraction < 0.5f ) brightness = 2.0f * fraction;
else brightness = 2.0f * ( 1.0f - fraction );
}
else if( FBitSet( flags, FBEAM_SHADEIN ))
{
brightness = fraction;
}
else if( FBitSet( flags, FBEAM_SHADEOUT ))
{
brightness = 1.0f - fraction;
}
VectorMA( source, fraction, delta, nextSeg.pos );
// distort using noise
if( scale != 0 )
{
factor = rgNoise[noiseIndex>>16] * scale;
if( FBitSet( flags, FBEAM_SINENOISE ))
{
float s, c;
SinCos( fraction * M_PI * length + freq, &s, &c );
VectorMA( nextSeg.pos, (factor * s), RI.vup, nextSeg.pos );
// rotate the noise along the perpendicluar axis a bit to keep the bolt from looking diagonal
VectorMA( nextSeg.pos, (factor * c), RI.vright, nextSeg.pos );
}
else
{
VectorMA( nextSeg.pos, factor, perp1, nextSeg.pos );
}
}
// specify the next segment.
nextSeg.width = width * 2.0f;
nextSeg.texcoord = vLast;
if( segs_drawn > 0 )
{
// Get a vector that is perpendicular to us and perpendicular to the beam.
// This is used to fatten the beam.
vec3_t vNormal, vAveNormal;
R_BeamComputeNormal( curSeg.pos, nextSeg.pos, vNormal );
if( segs_drawn > 1 )
{
// Average this with the previous normal
VectorAdd( vNormal, vLastNormal, vAveNormal );
VectorScale( vAveNormal, 0.5f, vAveNormal );
VectorNormalizeFast( vAveNormal );
}
else
{
VectorCopy( vNormal, vAveNormal );
}
VectorCopy( vNormal, vLastNormal );
// draw regular segment
VectorMA( curSeg.pos, ( curSeg.width * 0.5f ), vAveNormal, vPoint1 );
VectorMA( curSeg.pos, (-curSeg.width * 0.5f ), vAveNormal, vPoint2 );
pglTexCoord2f( 0.0f, curSeg.texcoord );
TriBrightness( brightness );
pglNormal3fv( vAveNormal );
pglVertex3fv( vPoint1 );
pglTexCoord2f( 1.0f, curSeg.texcoord );
TriBrightness( brightness );
pglNormal3fv( vAveNormal );
pglVertex3fv( vPoint2 );
}
curSeg = nextSeg;
segs_drawn++;
if( segs_drawn == total_segs )
{
// draw the last segment
VectorMA( curSeg.pos, ( curSeg.width * 0.5f ), vLastNormal, vPoint1 );
VectorMA( curSeg.pos, (-curSeg.width * 0.5f ), vLastNormal, vPoint2 );
// specify the points.
pglTexCoord2f( 0.0f, curSeg.texcoord );
TriBrightness( brightness );
pglNormal3fv( vLastNormal );
pglVertex3fv( vPoint1 );
pglTexCoord2f( 1.0f, curSeg.texcoord );
TriBrightness( brightness );
pglNormal3fv( vLastNormal );
pglVertex3fv( vPoint2 );
}
vLast += vStep; // Advance texture scroll (v axis only)
noiseIndex += noiseStep;
}
}
/*
================
R_DrawTorus
Draw beamtours
================
*/
void R_DrawTorus( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments )
{
int i, noiseIndex, noiseStep;
float div, length, fraction, factor, vLast, vStep;
vec3_t last1, last2, point, screen, screenLast, tmp, normal;
if( segments < 2 )
return;
if( segments > NOISE_DIVISIONS )
segments = NOISE_DIVISIONS;
length = VectorLength( delta ) * 0.01;
if( length < 0.5 ) length = 0.5; // don't lose all of the noise/texture on short beams
div = 1.0f / (segments - 1);
vStep = length * div; // Texture length texels per space pixel
// Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture)
vLast = fmod( freq * speed, 1 );
scale = scale * length;
// Iterator to resample noise waveform (it needs to be generated in powers of 2)
noiseStep = (int)((float)( NOISE_DIVISIONS - 1 ) * div * 65536.0f );
noiseIndex = 0;
for( i = 0; i < segments; i++ )
{
float s, c;
fraction = i * div;
SinCos( fraction * M_PI2, &s, &c );
point[0] = s * freq * delta[2] + source[0];
point[1] = c * freq * delta[2] + source[1];
point[2] = source[2];
// distort using noise
if( scale != 0 )
{
if(( noiseIndex >> 16 ) < NOISE_DIVISIONS )
{
factor = rgNoise[noiseIndex>>16] * scale;
VectorMA( point, factor, RI.vup, point );
// rotate the noise along the perpendicluar axis a bit to keep the bolt from looking diagonal
factor = rgNoise[noiseIndex>>16] * scale * cos( fraction * M_PI * 3 + freq );
VectorMA( point, factor, RI.vright, point );
}
}
// Transform point into screen space
TriWorldToScreen( point, screen );
if( i != 0 )
{
// build world-space normal to screen-space direction vector
VectorSubtract( screen, screenLast, tmp );
// we don't need Z, we're in screen space
tmp[2] = 0;
VectorNormalize( tmp );
VectorScale( RI.vup, -tmp[0], normal ); // Build point along noraml line (normal is -y, x)
VectorMA( normal, tmp[1], RI.vright, normal );
// Make a wide line
VectorMA( point, width, normal, last1 );
VectorMA( point, -width, normal, last2 );
vLast += vStep; // advance texture scroll (v axis only)
TriTexCoord2f( 1, vLast );
TriVertex3fv( last2 );
TriTexCoord2f( 0, vLast );
TriVertex3fv( last1 );
}
VectorCopy( screen, screenLast );
noiseIndex += noiseStep;
}
}
/*
================
R_DrawDisk
Draw beamdisk
================
*/
void R_DrawDisk( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments )
{
float div, length, fraction;
float w, vLast, vStep;
vec3_t point;
int i;
if( segments < 2 )
return;
if( segments > NOISE_DIVISIONS ) // UNDONE: Allow more segments?
segments = NOISE_DIVISIONS;
length = VectorLength( delta ) * 0.01f;
if( length < 0.5f ) length = 0.5f; // don't lose all of the noise/texture on short beams
div = 1.0f / (segments - 1);
vStep = length * div; // Texture length texels per space pixel
// scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture)
vLast = fmod( freq * speed, 1 );
scale = scale * length;
// clamp the beam width
w = fmod( freq, width ) * delta[2];
// NOTE: we must force the degenerate triangles to be on the edge
for( i = 0; i < segments; i++ )
{
float s, c;
fraction = i * div;
VectorCopy( source, point );
TriBrightness( 1.0f );
TriTexCoord2f( 1.0f, vLast );
TriVertex3fv( point );
SinCos( fraction * M_PI2, &s, &c );
point[0] = s * w + source[0];
point[1] = c * w + source[1];
point[2] = source[2];
TriBrightness( 1.0f );
TriTexCoord2f( 0.0f, vLast );
TriVertex3fv( point );
vLast += vStep; // advance texture scroll (v axis only)
}
}
/*
================
R_DrawCylinder
Draw beam cylinder
================
*/
void R_DrawCylinder( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments )
{
float div, length, fraction;
float vLast, vStep;
vec3_t point;
int i;
if( segments < 2 )
return;
if( segments > NOISE_DIVISIONS )
segments = NOISE_DIVISIONS;
length = VectorLength( delta ) * 0.01f;
if( length < 0.5f ) length = 0.5f; // don't lose all of the noise/texture on short beams
div = 1.0f / (segments - 1);
vStep = length * div; // texture length texels per space pixel
// Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture)
vLast = fmod( freq * speed, 1 );
scale = scale * length;
for ( i = 0; i < segments; i++ )
{
float s, c;
fraction = i * div;
SinCos( fraction * M_PI2, &s, &c );
point[0] = s * freq * delta[2] + source[0];
point[1] = c * freq * delta[2] + source[1];
point[2] = source[2] + width;
TriBrightness( 0 );
TriTexCoord2f( 1, vLast );
TriVertex3fv( point );
point[0] = s * freq * ( delta[2] + width ) + source[0];
point[1] = c * freq * ( delta[2] + width ) + source[1];
point[2] = source[2] - width;
TriBrightness( 1 );
TriTexCoord2f( 0, vLast );
TriVertex3fv( point );
vLast += vStep; // Advance texture scroll (v axis only)
}
}
/*
==============
R_DrawBeamFollow
drawi followed beam
==============
*/
void R_DrawBeamFollow( BEAM *pbeam, float frametime )
{
particle_t *pnew, *particles;
float fraction, div, vLast, vStep;
vec3_t last1, last2, tmp, screen;
vec3_t delta, screenLast, normal;
R_FreeDeadParticles( &pbeam->particles );
particles = pbeam->particles;
pnew = NULL;
div = 0;
if( FBitSet( pbeam->flags, FBEAM_STARTENTITY ))
{
if( particles )
{
VectorSubtract( particles->org, pbeam->source, delta );
div = VectorLength( delta );
if( div >= 32 && cl_free_particles )
{
pnew = cl_free_particles;
cl_free_particles = pnew->next;
}
}
else if( cl_free_particles )
{
pnew = cl_free_particles;
cl_free_particles = pnew->next;
div = 0;
}
}
if( pnew )
{
VectorCopy( pbeam->source, pnew->org );
pnew->die = cl.time + pbeam->amplitude;
VectorClear( pnew->vel );
pnew->next = particles;
pbeam->particles = pnew;
particles = pnew;
}
// nothing to draw
if( !particles ) return;
if( !pnew && div != 0 )
{
VectorCopy( pbeam->source, delta );
TriWorldToScreen( pbeam->source, screenLast );
TriWorldToScreen( particles->org, screen );
}
else if( particles && particles->next )
{
VectorCopy( particles->org, delta );
TriWorldToScreen( particles->org, screenLast );
TriWorldToScreen( particles->next->org, screen );
particles = particles->next;
}
else
{
return;
}
// UNDONE: This won't work, screen and screenLast must be extrapolated here to fix the
// first beam segment for this trail
// build world-space normal to screen-space direction vector
VectorSubtract( screen, screenLast, tmp );
// we don't need Z, we're in screen space
tmp[2] = 0;
VectorNormalize( tmp );
// Build point along noraml line (normal is -y, x)
VectorScale( RI.vup, tmp[0], normal ); // Build point along normal line (normal is -y, x)
VectorMA( normal, tmp[1], RI.vright, normal );
// Make a wide line
VectorMA( delta, pbeam->width, normal, last1 );
VectorMA( delta, -pbeam->width, normal, last2 );
div = 1.0 / pbeam->amplitude;
fraction = ( pbeam->die - cl.time ) * div;
vLast = 0.0;
vStep = 1.0;
while( particles )
{
TriBrightness( fraction );
TriTexCoord2f( 1, 1 );
TriVertex3fv( last2 );
TriBrightness( fraction );
TriTexCoord2f( 0, 1 );
TriVertex3fv( last1 );
// Transform point into screen space
TriWorldToScreen( particles->org, screen );
// Build world-space normal to screen-space direction vector
VectorSubtract( screen, screenLast, tmp );
// we don't need Z, we're in screen space
tmp[2] = 0;
VectorNormalize( tmp );
VectorScale( RI.vup, tmp[0], normal ); // Build point along noraml line (normal is -y, x)
VectorMA( normal, tmp[1], RI.vright, normal );
// Make a wide line
VectorMA( particles->org, pbeam->width, normal, last1 );
VectorMA( particles->org, -pbeam->width, normal, last2 );
vLast += vStep; // Advance texture scroll (v axis only)
if( particles->next != NULL )
{
fraction = (particles->die - cl.time) * div;
}
else
{
fraction = 0.0;
}
TriBrightness( fraction );
TriTexCoord2f( 0, 0 );
TriVertex3fv( last1 );
TriBrightness( fraction );
TriTexCoord2f( 1, 0 );
TriVertex3fv( last2 );
VectorCopy( screen, screenLast );
particles = particles->next;
}
// drift popcorn trail if there is a velocity
particles = pbeam->particles;
while( particles )
{
VectorMA( particles->org, frametime, particles->vel, particles->org );
particles = particles->next;
}
}
/*
================
R_DrawRing
Draw beamring
================
*/
void R_DrawRing( vec3_t source, vec3_t delta, float width, float amplitude, float freq, float speed, int segments )
{
int i, j, noiseIndex, noiseStep;
float div, length, fraction, factor, vLast, vStep;
vec3_t last1, last2, point, screen, screenLast;
vec3_t tmp, normal, center, xaxis, yaxis;
float radius, x, y, scale;
if( segments < 2 )
return;
VectorClear( screenLast );
segments = segments * M_PI;
if( segments > NOISE_DIVISIONS * 8 )
segments = NOISE_DIVISIONS * 8;
length = VectorLength( delta ) * 0.01f * M_PI;
if( length < 0.5f ) length = 0.5f; // Don't lose all of the noise/texture on short beams
div = 1.0f / ( segments - 1 );
vStep = length * div / 8.0f; // texture length texels per space pixel
// Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture)
vLast = fmod( freq * speed, 1.0f );
scale = amplitude * length / 8.0f;
// Iterator to resample noise waveform (it needs to be generated in powers of 2)
noiseStep = (int)((float)( NOISE_DIVISIONS - 1 ) * div * 65536.0f ) * 8;
noiseIndex = 0;
VectorScale( delta, 0.5f, delta );
VectorAdd( source, delta, center );
VectorCopy( delta, xaxis );
radius = VectorLength( xaxis );
// cull beamring
// --------------------------------
// Compute box center +/- radius
VectorSet( last1, radius, radius, scale );
VectorAdd( center, last1, tmp ); // maxs
VectorSubtract( center, last1, screen ); // mins
if( !cl.worldmodel )
return;
// is that box in PVS && frustum?
if( !Mod_BoxVisible( screen, tmp, Mod_GetCurrentVis( )) || R_CullBox( screen, tmp ))
{
return;
}
VectorSet( yaxis, xaxis[1], -xaxis[0], 0.0f );
VectorNormalize( yaxis );
VectorScale( yaxis, radius, yaxis );
j = segments / 8;
for( i = 0; i < segments + 1; i++ )
{
fraction = i * div;
SinCos( fraction * M_PI2, &x, &y );
VectorMAMAM( x, xaxis, y, yaxis, 1.0f, center, point );
// distort using noise
factor = rgNoise[(noiseIndex >> 16) & (NOISE_DIVISIONS - 1)] * scale;
VectorMA( point, factor, RI.vup, point );
// Rotate the noise along the perpendicluar axis a bit to keep the bolt from looking diagonal
factor = rgNoise[(noiseIndex >> 16) & (NOISE_DIVISIONS - 1)] * scale;
factor *= cos( fraction * M_PI * 24 + freq );
VectorMA( point, factor, RI.vright, point );
// Transform point into screen space
TriWorldToScreen( point, screen );
if( i != 0 )
{
// build world-space normal to screen-space direction vector
VectorSubtract( screen, screenLast, tmp );
// we don't need Z, we're in screen space
tmp[2] = 0;
VectorNormalize( tmp );
// Build point along normal line (normal is -y, x)
VectorScale( RI.vup, tmp[0], normal );
VectorMA( normal, tmp[1], RI.vright, normal );
// Make a wide line
VectorMA( point, width, normal, last1 );
VectorMA( point, -width, normal, last2 );
vLast += vStep; // Advance texture scroll (v axis only)
TriTexCoord2f( 1.0f, vLast );
TriVertex3fv( last2 );
TriTexCoord2f( 0.0f, vLast );
TriVertex3fv( last1 );
}
VectorCopy( screen, screenLast );
noiseIndex += noiseStep;
j--;
if( j == 0 && amplitude != 0 )
{
j = segments / 8;
FracNoise( rgNoise, NOISE_DIVISIONS );
}
}
}
/*
==============
R_BeamDraw
Update beam vars and draw it
==============
*/
void R_BeamDraw( BEAM *pbeam, float frametime )
{
model_t *sprite;
vec3_t delta;
SetBits( pbeam->flags, FBEAM_ISACTIVE );
if( Mod_GetType( pbeam->modelIndex ) != mod_sprite )
{
pbeam->flags &= ~FBEAM_ISACTIVE; // force to ignore
pbeam->die = cl.time;
return;
}
sprite = Mod_Handle( pbeam->modelIndex );
if( !sprite ) return;
// update frequency
pbeam->freq += frametime;
// generate fractal noise
if( frametime != 0.0f )
{
rgNoise[0] = 0;
rgNoise[NOISE_DIVISIONS] = 0;
}
if( pbeam->amplitude != 0 && frametime != 0.0f )
{
if( FBitSet( pbeam->flags, FBEAM_SINENOISE ))
SineNoise( rgNoise, NOISE_DIVISIONS );
else FracNoise( rgNoise, NOISE_DIVISIONS );
}
// update end points
if( FBitSet( pbeam->flags, FBEAM_STARTENTITY|FBEAM_ENDENTITY ))
{
// makes sure attachment[0] + attachment[1] are valid
if( !R_BeamRecomputeEndpoints( pbeam ))
{
ClearBits( pbeam->flags, FBEAM_ISACTIVE ); // force to ignore
return;
}
// compute segments from the new endpoints
VectorSubtract( pbeam->target, pbeam->source, delta );
VectorClear( pbeam->delta );
if( VectorLength( delta ) > 0.0000001f )
VectorCopy( delta, pbeam->delta );
if( pbeam->amplitude >= 0.50f )
pbeam->segments = VectorLength( pbeam->delta ) * 0.25f + 3.0f; // one per 4 pixels
else pbeam->segments = VectorLength( pbeam->delta ) * 0.075f + 3.0f; // one per 16 pixels
}
if( pbeam->type == TE_BEAMPOINTS && R_BeamCull( pbeam->source, pbeam->target, 0 ))
{
ClearBits( pbeam->flags, FBEAM_ISACTIVE );
return;
}
// don't draw really short or inactive beams
if( !FBitSet( pbeam->flags, FBEAM_ISACTIVE ) || VectorLength( pbeam->delta ) < 0.1f )
{
return;
}
if( pbeam->flags & ( FBEAM_FADEIN|FBEAM_FADEOUT ))
{
// update life cycle
pbeam->t = pbeam->freq + ( pbeam->die - cl.time );
if( pbeam->t != 0.0f ) pbeam->t = 1.0f - pbeam->freq / pbeam->t;
}
if( pbeam->type == TE_BEAMHOSE )
{
float flDot;
VectorSubtract( pbeam->target, pbeam->source, delta );
VectorNormalize( delta );
flDot = DotProduct( delta, RI.vforward );
// abort if the player's looking along it away from the source
if( flDot > 0 )
{
return;
}
else
{
float flFade = pow( flDot, 10 );
vec3_t localDir, vecProjection, tmp;
float flDistance;
// fade the beam if the player's not looking at the source
VectorSubtract( RI.vieworg, pbeam->source, localDir );
flDot = DotProduct( delta, localDir );
VectorScale( delta, flDot, vecProjection );
VectorSubtract( localDir, vecProjection, tmp );
flDistance = VectorLength( tmp );
if( flDistance > 30 )
{
flDistance = 1.0f - (( flDistance - 30.0f ) / 64.0f );
if( flDistance <= 0 ) flFade = 0;
else flFade *= pow( flDistance, 3 );
}
if( flFade < ( 1.0f / 255.0f ))
return;
// FIXME: needs to be testing
pbeam->brightness *= flFade;
}
}
TriRenderMode( FBitSet( pbeam->flags, FBEAM_SOLID ) ? kRenderNormal : kRenderTransAdd );
if( !TriSpriteTexture( sprite, (int)(pbeam->frame + pbeam->frameRate * cl.time) % pbeam->frameCount ))
{
ClearBits( pbeam->flags, FBEAM_ISACTIVE );
return;
}
if( pbeam->type == TE_BEAMFOLLOW )
{
cl_entity_t *pStart;
// HACKHACK: get brightness from head entity
pStart = R_BeamGetEntity( pbeam->startEntity );
if( pStart && pStart->curstate.rendermode != kRenderNormal )
pbeam->brightness = CL_FxBlend( pStart ) / 255.0f;
}
if( FBitSet( pbeam->flags, FBEAM_FADEIN ))
TriColor4f( pbeam->r, pbeam->g, pbeam->b, pbeam->t * pbeam->brightness );
else if( FBitSet( pbeam->flags, FBEAM_FADEOUT ))
TriColor4f( pbeam->r, pbeam->g, pbeam->b, ( 1.0f - pbeam->t ) * pbeam->brightness );
else TriColor4f( pbeam->r, pbeam->g, pbeam->b, pbeam->brightness );
switch( pbeam->type )
{
case TE_BEAMTORUS:
GL_Cull( GL_NONE );
TriBegin( TRI_TRIANGLE_STRIP );
R_DrawTorus( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments );
TriEnd();
break;
case TE_BEAMDISK:
GL_Cull( GL_NONE );
TriBegin( TRI_TRIANGLE_STRIP );
R_DrawDisk( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments );
TriEnd();
break;
case TE_BEAMCYLINDER:
GL_Cull( GL_NONE );
TriBegin( TRI_TRIANGLE_STRIP );
R_DrawCylinder( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments );
TriEnd();
break;
case TE_BEAMPOINTS:
case TE_BEAMHOSE:
TriBegin( TRI_TRIANGLE_STRIP );
R_DrawSegs( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments, pbeam->flags );
TriEnd();
break;
case TE_BEAMFOLLOW:
TriBegin( TRI_QUADS );
R_DrawBeamFollow( pbeam, frametime );
TriEnd();
break;
case TE_BEAMRING:
GL_Cull( GL_NONE );
TriBegin( TRI_TRIANGLE_STRIP );
R_DrawRing( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments );
TriEnd();
break;
}
GL_Cull( GL_FRONT );
}
/*
==============
R_BeamDrawCustomEntity
initialize beam from server entity
==============
*/
void R_BeamDrawCustomEntity( cl_entity_t *ent )
{
BEAM beam;
float amp = ent->curstate.body / 100.0f;
float blend = CL_FxBlend( ent ) / 255.0f;
float r, g, b;
int beamFlags;
r = ent->curstate.rendercolor.r / 255.0f;
g = ent->curstate.rendercolor.g / 255.0f;
b = ent->curstate.rendercolor.b / 255.0f;
R_BeamSetup( &beam, ent->origin, ent->angles, ent->curstate.modelindex, 0, ent->curstate.scale, amp, blend, ent->curstate.animtime );
R_BeamSetAttributes( &beam, r, g, b, ent->curstate.framerate, ent->curstate.frame );
beam.pFollowModel = NULL;
switch( ent->curstate.rendermode & 0x0F )
{
case BEAM_ENTPOINT:
beam.type = TE_BEAMPOINTS;
if( ent->curstate.sequence )
{
SetBits( beam.flags, FBEAM_STARTENTITY );
beam.startEntity = ent->curstate.sequence;
}
if( ent->curstate.skin )
{
SetBits( beam.flags, FBEAM_ENDENTITY );
beam.endEntity = ent->curstate.skin;
}
break;
case BEAM_ENTS:
beam.type = TE_BEAMPOINTS;
SetBits( beam.flags, FBEAM_STARTENTITY | FBEAM_ENDENTITY );
beam.startEntity = ent->curstate.sequence;
beam.endEntity = ent->curstate.skin;
break;
case BEAM_HOSE:
beam.type = TE_BEAMHOSE;
break;
case BEAM_POINTS:
// already set up
break;
}
beamFlags = ( ent->curstate.rendermode & 0xF0 );
if( FBitSet( beamFlags, BEAM_FSINE ))
SetBits( beam.flags, FBEAM_SINENOISE );
if( FBitSet( beamFlags, BEAM_FSOLID ))
SetBits( beam.flags, FBEAM_SOLID );
if( FBitSet( beamFlags, BEAM_FSHADEIN ))
SetBits( beam.flags, FBEAM_SHADEIN );
if( FBitSet( beamFlags, BEAM_FSHADEOUT ))
SetBits( beam.flags, FBEAM_SHADEOUT );
// draw it
R_BeamDraw( &beam, tr.frametime );
}
/*
==============================================================
VIEWBEAMS MANAGEMENT
==============================================================
*/
BEAM *cl_active_beams;
BEAM *cl_free_beams;
BEAM *cl_viewbeams = NULL; // beams pool
cl_entity_t *cl_custombeams[MAX_VISIBLE_PACKET]; // to avoid check of all the ents
/*
================
CL_InitViewBeams
================
*/
void CL_InitViewBeams( void )
{
cl_viewbeams = Mem_Alloc( cls.mempool, sizeof( BEAM ) * GI->max_beams );
CL_ClearViewBeams();
}
/*
================
CL_ClearViewBeams
================
*/
void CL_ClearViewBeams( void )
{
int i;
if( !cl_viewbeams ) return;
// clear beams
cl_free_beams = cl_viewbeams;
cl_active_beams = NULL;
for( i = 0; i < GI->max_beams - 1; i++ )
cl_viewbeams[i].next = &cl_viewbeams[i+1];
cl_viewbeams[GI->max_beams - 1].next = NULL;
}
/*
================
CL_FreeViewBeams
================
*/
void CL_FreeViewBeams( void )
{
if( cl_viewbeams )
Mem_Free( cl_viewbeams );
cl_viewbeams = NULL;
}
/*
================
CL_AddCustomBeam
Add the beam that encoded as custom entity
================
*/
void CL_AddCustomBeam( cl_entity_t *pEnvBeam )
{
if( cl.num_custombeams >= MAX_VISIBLE_PACKET )
{
MsgDev( D_ERROR, "Too many custom beams %d!\n", cl.num_custombeams );
return;
}
if( pEnvBeam )
{
cl_custombeams[cl.num_custombeams] = pEnvBeam;
cl.num_custombeams++;
}
}
/*
==============
CL_KillDeadBeams
==============
*/
void CL_KillDeadBeams( cl_entity_t *pDeadEntity )
{
BEAM *pbeam;
BEAM *pnewlist;
BEAM *pnext;
particle_t *pHead; // build a new list to replace cl_active_beams.
pbeam = cl_active_beams; // old list.
pnewlist = NULL; // new list.
while( pbeam )
{
pnext = pbeam->next;
// link into new list.
if( R_BeamGetEntity( pbeam->startEntity ) != pDeadEntity )
{
pbeam->next = pnewlist;
pnewlist = pbeam;
pbeam = pnext;
continue;
}
pbeam->flags &= ~(FBEAM_STARTENTITY | FBEAM_ENDENTITY);
if( pbeam->type != TE_BEAMFOLLOW )
{
// remove beam
pbeam->die = cl.time - 0.1f;
// kill off particles
pHead = pbeam->particles;
while( pHead )
{
pHead->die = cl.time - 0.1f;
pHead = pHead->next;
}
// free the beam
R_BeamFree( pbeam );
}
else
{
// stay active
pbeam->next = pnewlist;
pnewlist = pbeam;
}
pbeam = pnext;
}
// We now have a new list with the bogus stuff released.
cl_active_beams = pnewlist;
}
/*
==============
CL_BeamAttemptToDie
Check for expired beams
==============
*/
qboolean CL_BeamAttemptToDie( BEAM *pBeam )
{
Assert( pBeam != NULL );
// premanent beams never die automatically
if( FBitSet( pBeam->flags, FBEAM_FOREVER ))
return false;
if( pBeam->type == TE_BEAMFOLLOW && pBeam->particles )
{
// wait for all trails are dead
return false;
}
// other beams
if( pBeam->die > cl.time )
return false;
return true;
}
/*
==============
CL_DrawBeams
draw beam loop
==============
*/
void CL_DrawBeams( int fTrans )
{
BEAM *pBeam, *pNext;
BEAM *pPrev = NULL;
int i, flags;
if( !cl_draw_beams->value )
return;
pglShadeModel( GL_SMOOTH );
pglDepthMask( fTrans ? GL_FALSE : GL_TRUE );
// server beams don't allocate beam chains
// all params are stored in cl_entity_t
for( i = 0; i < cl.num_custombeams; i++ )
{
RI.currentbeam = cl_custombeams[i];
flags = RI.currentbeam->curstate.rendermode & 0xF0;
if( fTrans && FBitSet( flags, FBEAM_SOLID ))
continue;
if( !fTrans && !FBitSet( flags, FBEAM_SOLID ))
continue;
R_BeamDrawCustomEntity( RI.currentbeam );
r_stats.c_view_beams_count++;
}
RI.currentbeam = NULL;
// draw temporary entity beams
for( pBeam = cl_active_beams; pBeam; pBeam = pNext )
{
// need to store the next one since we may delete this one
pNext = pBeam->next;
if( fTrans && FBitSet( pBeam->flags, FBEAM_SOLID ))
continue;
if( !fTrans && !FBitSet( pBeam->flags, FBEAM_SOLID ))
continue;
// retire old beams
if( CL_BeamAttemptToDie( pBeam ))
{
// reset links
if( pPrev ) pPrev->next = pNext;
else cl_active_beams = pNext;
// free the beam
R_BeamFree( pBeam );
pBeam = NULL;
continue;
}
R_BeamDraw( pBeam, cl.time - cl.oldtime );
r_stats.c_view_beams_count++;
pPrev = pBeam;
}
pglShadeModel( GL_FLAT );
pglDepthMask( GL_TRUE );
}
/*
==============
R_BeamKill
Remove beam attached to specified entity
and all particle trails (if this is a beamfollow)
==============
*/
void R_BeamKill( int deadEntity )
{
cl_entity_t *pDeadEntity;
pDeadEntity = R_BeamGetEntity( deadEntity );
if( !pDeadEntity ) return;
CL_KillDeadBeams( pDeadEntity );
}
/*
==============
R_BeamEnts
Create beam between two ents
==============
*/
BEAM *R_BeamEnts( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness,
float speed, int startFrame, float framerate, float r, float g, float b )
{
cl_entity_t *start, *end;
BEAM *pbeam;
// need a valid model.
if( Mod_GetType( modelIndex ) != mod_sprite )
return NULL;
start = R_BeamGetEntity( startEnt );
end = R_BeamGetEntity( endEnt );
if( !start || !end )
return NULL;
// don't start temporary beams out of the PVS
if( life != 0 && ( !start->model || !end->model ))
return NULL;
pbeam = R_BeamLightning( vec3_origin, vec3_origin, modelIndex, life, width, amplitude, brightness, speed );
if( !pbeam ) return NULL;
pbeam->type = TE_BEAMPOINTS;
SetBits( pbeam->flags, FBEAM_STARTENTITY | FBEAM_ENDENTITY );
if( life == 0 ) SetBits( pbeam->flags, FBEAM_FOREVER );
pbeam->startEntity = startEnt;
pbeam->endEntity = endEnt;
R_BeamSetAttributes( pbeam, r, g, b, framerate, startFrame );
return pbeam;
}
/*
==============
R_BeamPoints
Create beam between two points
==============
*/
BEAM *R_BeamPoints( vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude,
float brightness, float speed, int startFrame, float framerate, float r, float g, float b )
{
BEAM *pbeam;
if( life != 0 && R_BeamCull( start, end, true ))
return NULL;
pbeam = R_BeamAlloc();
if( !pbeam ) return NULL;
pbeam->die = cl.time;
if( modelIndex < 0 )
return NULL;
R_BeamSetup( pbeam, start, end, modelIndex, life, width, amplitude, brightness, speed );
if( life == 0 ) SetBits( pbeam->flags, FBEAM_FOREVER );
R_BeamSetAttributes( pbeam, r, g, b, framerate, startFrame );
return pbeam;
}
/*
==============
R_BeamCirclePoints
Create beam cicrle
==============
*/
BEAM *R_BeamCirclePoints( int type, vec3_t start, vec3_t end, int modelIndex, float life, float width,
float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b )
{
BEAM *pbeam = R_BeamLightning( start, end, modelIndex, life, width, amplitude, brightness, speed );
if( !pbeam ) return NULL;
pbeam->type = type;
if( life == 0 ) SetBits( pbeam->flags, FBEAM_FOREVER );
R_BeamSetAttributes( pbeam, r, g, b, framerate, startFrame );
return pbeam;
}
/*
==============
R_BeamEntPoint
Create beam between entity and point
==============
*/
BEAM *R_BeamEntPoint( int startEnt, vec3_t end, int modelIndex, float life, float width, float amplitude,
float brightness, float speed, int startFrame, float framerate, float r, float g, float b )
{
BEAM *pbeam;
cl_entity_t *start;
start = R_BeamGetEntity( startEnt );
if( !start ) return NULL;
if( life == 0 && !start->model )
return NULL;
pbeam = R_BeamAlloc();
if ( !pbeam ) return NULL;
pbeam->die = cl.time;
if( modelIndex < 0 )
return NULL;
R_BeamSetup( pbeam, vec3_origin, end, modelIndex, life, width, amplitude, brightness, speed );
pbeam->type = TE_BEAMPOINTS;
SetBits( pbeam->flags, FBEAM_STARTENTITY );
if( life == 0 ) SetBits( pbeam->flags, FBEAM_FOREVER );
pbeam->startEntity = startEnt;
pbeam->endEntity = 0;
R_BeamSetAttributes( pbeam, r, g, b, framerate, startFrame );
return pbeam;
}
/*
==============
R_BeamRing
Create beam between two ents
==============
*/
BEAM *R_BeamRing( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness,
float speed, int startFrame, float framerate, float r, float g, float b )
{
BEAM *pbeam;
cl_entity_t *start, *end;
start = R_BeamGetEntity( startEnt );
end = R_BeamGetEntity( endEnt );
if( !start || !end )
return NULL;
if( life != 0 && ( !start->model || !end->model ))
return NULL;
pbeam = R_BeamLightning( vec3_origin, vec3_origin, modelIndex, life, width, amplitude, brightness, speed );
if( !pbeam ) return NULL;
pbeam->type = TE_BEAMRING;
SetBits( pbeam->flags, FBEAM_STARTENTITY | FBEAM_ENDENTITY );
if( life == 0 ) SetBits( pbeam->flags, FBEAM_FOREVER );
pbeam->startEntity = startEnt;
pbeam->endEntity = endEnt;
R_BeamSetAttributes( pbeam, r, g, b, framerate, startFrame );
return pbeam;
}
/*
==============
R_BeamFollow
Create beam following with entity
==============
*/
BEAM *R_BeamFollow( int startEnt, int modelIndex, float life, float width, float r, float g, float b, float brightness )
{
BEAM *pbeam = R_BeamAlloc();
if( !pbeam ) return NULL;
pbeam->die = cl.time;
if( modelIndex < 0 )
return NULL;
R_BeamSetup( pbeam, vec3_origin, vec3_origin, modelIndex, life, width, life, brightness, 1.0f );
pbeam->type = TE_BEAMFOLLOW;
SetBits( pbeam->flags, FBEAM_STARTENTITY );
pbeam->startEntity = startEnt;
R_BeamSetAttributes( pbeam, r, g, b, 1.0f, 0 );
return pbeam;
}
/*
==============
R_BeamSprite
Create a beam with sprite at the end
Valve legacy
==============
*/
void R_BeamSprite( vec3_t start, vec3_t end, int beamIndex, int spriteIndex )
{
R_BeamPoints( start, end, beamIndex, 0.01f, 0.4f, 0, COM_RandomFloat( 0.5f, 0.655f ), 5.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f );
R_TempSprite( end, vec3_origin, 0.1f, spriteIndex, kRenderTransAdd, kRenderFxNone, 0.35f, 0.01f, 0.0f );
}
/*
==============
CL_ParseViewBeam
handle beam messages
==============
*/
void CL_ParseViewBeam( sizebuf_t *msg, int beamType )
{
vec3_t start, end;
int modelIndex, startFrame;
float frameRate, life, width;
int startEnt, endEnt;
float noise, speed;
float r, g, b, a;
switch( beamType )
{
case TE_BEAMPOINTS:
start[0] = MSG_ReadCoord( msg );
start[1] = MSG_ReadCoord( msg );
start[2] = MSG_ReadCoord( msg );
end[0] = MSG_ReadCoord( msg );
end[1] = MSG_ReadCoord( msg );
end[2] = MSG_ReadCoord( msg );
modelIndex = MSG_ReadShort( msg );
startFrame = MSG_ReadByte( msg );
frameRate = (float)MSG_ReadByte( msg );
life = (float)(MSG_ReadByte( msg ) * 0.1f);
width = (float)(MSG_ReadByte( msg ) * 0.1f);
noise = (float)(MSG_ReadByte( msg ) * 0.01f);
r = (float)MSG_ReadByte( msg ) / 255.0f;
g = (float)MSG_ReadByte( msg ) / 255.0f;
b = (float)MSG_ReadByte( msg ) / 255.0f;
a = (float)MSG_ReadByte( msg ) / 255.0f;
speed = (float)(MSG_ReadByte( msg ) * 0.1f);
R_BeamPoints( start, end, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b );
break;
case TE_BEAMENTPOINT:
startEnt = MSG_ReadShort( msg );
end[0] = MSG_ReadCoord( msg );
end[1] = MSG_ReadCoord( msg );
end[2] = MSG_ReadCoord( msg );
modelIndex = MSG_ReadShort( msg );
startFrame = MSG_ReadByte( msg );
frameRate = (float)MSG_ReadByte( msg );
life = (float)(MSG_ReadByte( msg ) * 0.1f);
width = (float)(MSG_ReadByte( msg ) * 0.1f);
noise = (float)(MSG_ReadByte( msg ) * 0.01f);
r = (float)MSG_ReadByte( msg ) / 255.0f;
g = (float)MSG_ReadByte( msg ) / 255.0f;
b = (float)MSG_ReadByte( msg ) / 255.0f;
a = (float)MSG_ReadByte( msg ) / 255.0f;
speed = (float)(MSG_ReadByte( msg ) * 0.1f);
R_BeamEntPoint( startEnt, end, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b );
break;
case TE_LIGHTNING:
start[0] = MSG_ReadCoord( msg );
start[1] = MSG_ReadCoord( msg );
start[2] = MSG_ReadCoord( msg );
end[0] = MSG_ReadCoord( msg );
end[1] = MSG_ReadCoord( msg );
end[2] = MSG_ReadCoord( msg );
life = (float)(MSG_ReadByte( msg ) * 0.1f);
width = (float)(MSG_ReadByte( msg ) * 0.1f);
noise = (float)(MSG_ReadByte( msg ) * 0.01f);
modelIndex = MSG_ReadShort( msg );
R_BeamLightning( start, end, modelIndex, life, width, noise, 0.6F, 3.5f );
break;
case TE_BEAMENTS:
startEnt = MSG_ReadShort( msg );
endEnt = MSG_ReadShort( msg );
modelIndex = MSG_ReadShort( msg );
startFrame = MSG_ReadByte( msg );
frameRate = (float)(MSG_ReadByte( msg ) * 0.1f);
life = (float)(MSG_ReadByte( msg ) * 0.1f);
width = (float)(MSG_ReadByte( msg ) * 0.1f);
noise = (float)(MSG_ReadByte( msg ) * 0.01f);
r = (float)MSG_ReadByte( msg ) / 255.0f;
g = (float)MSG_ReadByte( msg ) / 255.0f;
b = (float)MSG_ReadByte( msg ) / 255.0f;
a = (float)MSG_ReadByte( msg ) / 255.0f;
speed = (float)(MSG_ReadByte( msg ) * 0.1f);
R_BeamEnts( startEnt, endEnt, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b );
break;
case TE_BEAM:
MsgDev( D_ERROR, "TE_BEAM is obsolete\n" );
break;
case TE_BEAMSPRITE:
start[0] = MSG_ReadCoord( msg );
start[1] = MSG_ReadCoord( msg );
start[2] = MSG_ReadCoord( msg );
end[0] = MSG_ReadCoord( msg );
end[1] = MSG_ReadCoord( msg );
end[2] = MSG_ReadCoord( msg );
modelIndex = MSG_ReadShort( msg ); // beam model
startFrame = MSG_ReadShort( msg ); // sprite model
R_BeamSprite( start, end, modelIndex, startFrame );
break;
case TE_BEAMTORUS:
case TE_BEAMDISK:
case TE_BEAMCYLINDER:
start[0] = MSG_ReadCoord( msg );
start[1] = MSG_ReadCoord( msg );
start[2] = MSG_ReadCoord( msg );
end[0] = MSG_ReadCoord( msg );
end[1] = MSG_ReadCoord( msg );
end[2] = MSG_ReadCoord( msg );
modelIndex = MSG_ReadShort( msg );
startFrame = MSG_ReadByte( msg );
frameRate = (float)(MSG_ReadByte( msg ));
life = (float)(MSG_ReadByte( msg ) * 0.1f);
width = (float)(MSG_ReadByte( msg ) * 0.1f);
noise = (float)(MSG_ReadByte( msg ) * 0.01f);
r = (float)MSG_ReadByte( msg ) / 255.0f;
g = (float)MSG_ReadByte( msg ) / 255.0f;
b = (float)MSG_ReadByte( msg ) / 255.0f;
a = (float)MSG_ReadByte( msg ) / 255.0f;
speed = (float)MSG_ReadByte( msg );
R_BeamCirclePoints( beamType, start, end, modelIndex, life, width, noise, a, speed / 10.0f, startFrame, frameRate, r, g, b );
break;
case TE_BEAMFOLLOW:
startEnt = MSG_ReadShort( msg );
modelIndex = MSG_ReadShort( msg );
life = (float)(MSG_ReadByte( msg ) * 0.1f);
width = (float)MSG_ReadByte( msg );
r = (float)MSG_ReadByte( msg ) / 255.0f;
g = (float)MSG_ReadByte( msg ) / 255.0f;
b = (float)MSG_ReadByte( msg ) / 255.0f;
a = (float)MSG_ReadByte( msg ) / 255.0f;
R_BeamFollow( startEnt, modelIndex, life, width, r, g, b, a );
break;
case TE_BEAMRING:
startEnt = MSG_ReadShort( msg );
endEnt = MSG_ReadShort( msg );
modelIndex = MSG_ReadShort( msg );
startFrame = MSG_ReadByte( msg );
frameRate = (float)MSG_ReadByte( msg );
life = (float)(MSG_ReadByte( msg ) * 0.1f);
width = (float)(MSG_ReadByte( msg ) * 0.1f);
noise = (float)(MSG_ReadByte( msg ) * 0.01f);
r = (float)MSG_ReadByte( msg ) / 255.0f;
g = (float)MSG_ReadByte( msg ) / 255.0f;
b = (float)MSG_ReadByte( msg ) / 255.0f;
a = (float)MSG_ReadByte( msg ) / 255.0f;
speed = (float)(MSG_ReadByte( msg ) * 0.1f);
R_BeamRing( startEnt, endEnt, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b );
break;
case TE_BEAMHOSE:
MsgDev( D_ERROR, "TE_BEAMHOSE is obsolete\n" );
break;
case TE_KILLBEAM:
startEnt = MSG_ReadShort( msg );
R_BeamKill( startEnt );
break;
}
}
/*
===============
CL_ReadLineFile_f
Optimized version of pointfile - use beams instead of particles
===============
*/
void CL_ReadLineFile_f( void )
{
char *afile, *pfile;
vec3_t p1, p2;
int count, modelIndex;
char filename[64];
string token;
Q_snprintf( filename, sizeof( filename ), "maps/%s.lin", 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;
modelIndex = CL_FindModelIndex( "sprites/laserbeam.spr" );
while( 1 )
{
pfile = COM_ParseFile( pfile, token );
if( !pfile ) break;
p1[0] = Q_atof( token );
pfile = COM_ParseFile( pfile, token );
if( !pfile ) break;
p1[1] = Q_atof( token );
pfile = COM_ParseFile( pfile, token );
if( !pfile ) break;
p1[2] = Q_atof( token );
pfile = COM_ParseFile( pfile, token );
if( !pfile ) break;
if( token[0] != '-' )
{
MsgDev( D_ERROR, "%s is corrupted\n", filename );
break;
}
pfile = COM_ParseFile( pfile, token );
if( !pfile ) break;
p2[0] = Q_atof( token );
pfile = COM_ParseFile( pfile, token );
if( !pfile ) break;
p2[1] = Q_atof( token );
pfile = COM_ParseFile( pfile, token );
if( !pfile ) break;
p2[2] = Q_atof( token );
count++;
if( !R_BeamPoints( p1, p2, modelIndex, 99999, 2, 0, 255, 0, 0, 0, 255.0f, 0.0f, 0.0f ))
{
if( Mod_GetType( modelIndex ) != mod_sprite )
MsgDev( D_ERROR, "CL_ReadLineFile: failed to load sprites/laserbeam.spr!\n" );
else MsgDev( D_ERROR, "CL_ReadLineFile: not enough free beams!\n" );
break;
}
}
Mem_Free( afile );
if( count ) Msg( "%i lines read\n", count );
else Msg( "map %s has no leaks!\n", clgame.mapname );
}