xash3d-fwgs/ref_gl/gl_beams.c
Gleb Mazovetskiy 5e0a0765ce Trim all trailing whitespace
The `.editorconfig` file in this repo is configured to trim all trailing
whitespace regardless of whether the line is modified.

Trims all trailing whitespace in the repository to make the codebase easier
to work with in editors that respect `.editorconfig`.

`git blame` becomes less useful on these lines but it already isn't very useful.

Commands:

```
find . -type f -name '*.h' -exec sed --in-place 's/[[:space:]]\+$//' {} \+
find . -type f -name '*.c' -exec sed --in-place 's/[[:space:]]\+$//' {} \+
```
2021-01-04 20:55:10 +03:00

1301 lines
31 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 "gl_local.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 "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 * gEngfuncs.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_F / (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 );
}
/*
==============
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( gEngfuncs.Mod_BoxVisible( mins, maxs, Mod_GetCurrentVis( )))
{
if( pvsOnly || !R_CullBox( mins, maxs ))
{
// beam is visible
return false;
}
}
// beam is culled
return true;
}
/*
================
CL_AddCustomBeam
Add the beam that encoded as custom entity
================
*/
void CL_AddCustomBeam( cl_entity_t *pEnvBeam )
{
if( tr.draw_list->num_beam_entities >= MAX_VISIBLE_PACKET )
{
gEngfuncs.Con_Printf( S_ERROR "Too many beams %d!\n", tr.draw_list->num_beam_entities );
return;
}
if( pEnvBeam )
{
tr.draw_list->beam_entities[tr.draw_list->num_beam_entities] = pEnvBeam;
tr.draw_list->num_beam_entities++;
}
}
/*
==============================================================
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;
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 * 2.0f;
}
// Iterator to resample noise waveform (it needs to be generated in powers of 2)
noiseStep = (int)((float)( NOISE_DIVISIONS - 1 ) * div * 65536.0f );
brightness = 1.0f;
noiseIndex = 0;
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;
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_F * 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( FBitSet( flags, FBEAM_SHADEIN ) && FBitSet( flags, FBEAM_SHADEOUT ))
{
if( fraction < 0.5f ) brightness = fraction;
else brightness = ( 1.0f - fraction );
}
else if( FBitSet( flags, FBEAM_SHADEIN ))
{
brightness = fraction;
}
else if( FBitSet( flags, FBEAM_SHADEOUT ))
{
brightness = 1.0f - fraction;
}
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.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;
// 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_F, &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_F * 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 * 0.1f ) * 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_F, &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_F, &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;
gEngfuncs.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 )
{
pnew = gEngfuncs.CL_AllocParticleFast();
}
}
else
{
pnew = gEngfuncs.CL_AllocParticleFast();
}
}
if( pnew )
{
VectorCopy( pbeam->source, pnew->org );
pnew->die = gpGlobals->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.0f / pbeam->amplitude;
fraction = ( pbeam->die - gpGlobals->time ) * div;
vLast = 0.0f;
vStep = 1.0f;
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 - gpGlobals->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_F;
if( segments > NOISE_DIVISIONS * 8 )
segments = NOISE_DIVISIONS * 8;
length = VectorLength( delta ) * 0.01f * M_PI_F;
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( !WORLDMODEL )
return;
// is that box in PVS && frustum?
if( !gEngfuncs.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_F, &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_F * 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_BeamComputePoint
compute attachment point for beam
==============
*/
static qboolean R_BeamComputePoint( int beamEnt, vec3_t pt )
{
cl_entity_t *ent;
int attach;
ent = gEngfuncs.R_BeamGetEntity( beamEnt );
if( beamEnt < 0 )
attach = BEAMENT_ATTACHMENT( -beamEnt );
else attach = BEAMENT_ATTACHMENT( beamEnt );
if( !ent )
{
gEngfuncs.Con_DPrintf( S_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 == ENGINE_GET_PARM( PARM_PLAYER_INDEX ) )
{
vec3_t simorg;
gEngfuncs.GetPredictedOrigin( simorg );
VectorCopy( 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 = gEngfuncs.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 = gEngfuncs.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 = gpGlobals->time;
return false;
}
else
{
return false;
}
}
if( FBitSet( pbeam->flags, FBEAM_STARTENTITY ) && !FBitSet( pbeam->flags, FBEAM_STARTVISIBLE ))
return false;
return true;
}
/*
==============
R_BeamDraw
Update beam vars and draw it
==============
*/
void R_BeamDraw( BEAM *pbeam, float frametime )
{
model_t *model;
vec3_t delta;
model = gEngfuncs.pfnGetModelByIndex( pbeam->modelIndex );
SetBits( pbeam->flags, FBEAM_ISACTIVE );
if( !model || model->type != mod_sprite )
{
pbeam->flags &= ~FBEAM_ISACTIVE; // force to ignore
pbeam->die = gpGlobals->time;
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 - gpGlobals->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( model, (int)(pbeam->frame + pbeam->frameRate * gpGlobals->time) % pbeam->frameCount ))
{
ClearBits( pbeam->flags, FBEAM_ISACTIVE );
return;
}
if( pbeam->type == TE_BEAMFOLLOW )
{
cl_entity_t *pStart;
// XASH SPECIFIC: get brightness from head entity
pStart = gEngfuncs.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_stats.c_view_beams_count++;
}
/*
==============
R_BeamSetAttributes
set beam attributes
==============
*/
static 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_BeamSetup
generic function. all beams must be
passed through this
==============
*/
static 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 = gEngfuncs.pfnGetModelByIndex( modelIndex );
if( !sprite ) return;
pbeam->type = BEAM_POINTS;
pbeam->modelIndex = modelIndex;
pbeam->frame = 0;
pbeam->frameRate = 0;
pbeam->frameCount = sprite->numframes;
VectorCopy( start, pbeam->source );
VectorCopy( end, pbeam->target );
VectorSubtract( end, start, pbeam->delta );
pbeam->freq = speed * gpGlobals->time;
pbeam->die = life + gpGlobals->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_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 );
}
/*
==============
CL_DrawBeams
draw beam loop
==============
*/
void CL_DrawBeams( int fTrans, BEAM *active_beams )
{
BEAM *pBeam;
int i, flags;
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 < tr.draw_list->num_beam_entities; i++ )
{
RI.currentbeam = tr.draw_list->beam_entities[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 = active_beams; pBeam; pBeam = pBeam->next )
{
if( fTrans && FBitSet( pBeam->flags, FBEAM_SOLID ))
continue;
if( !fTrans && !FBitSet( pBeam->flags, FBEAM_SOLID ))
continue;
R_BeamDraw( pBeam, gpGlobals->time - gpGlobals->oldtime );
}
pglShadeModel( GL_FLAT );
pglDepthMask( GL_TRUE );
}