/*
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.
*/

#ifndef XASH_DEDICATED

#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"
#include "cl_tent.h"

#define CURVEDBEAM 0
#define NOISE_DIVISIONS	64	// don't touch - many tripmines cause the crash when it equal 128
typedef struct
{
	vec3_t	pos;
	vec3_t	color;
	float	texcoord;	// Y texture coordinate
	float	width;
	float	alpha;
} beamseg_t;

static float	rgNoise[NOISE_DIVISIONS+1];	// global noise array

/*
==============================================================
VIEWBEAMS DRAW METHODS
==============================================================
*/

// freq2 += step * 0.1;
// Fractal noise generator, power of 2 wavelength
static void FracNoise( float *noise, int divs, float scale )
{
	int	div2;

	div2 = divs >> 1;
	if( divs < 2 ) return;

	// noise is normalized to +/- scale
	noise[div2] = ( noise[0] + noise[divs] ) * 0.5f + scale * Com_RandomFloat( -1.0f, 1.0f );

	if( div2 > 1 )
	{
		FracNoise( &noise[div2], div2, scale * 0.5f );
		FracNoise( noise, div2, scale * 0.5f );
	}
}

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;
	}
}

static cl_entity_t *CL_GetBeamEntityByIndex( int index )
{
	cl_entity_t	*ent;

	if( index < 0 )
	{
		index = BEAMENT_ENTITY( -index );
		return clgame.dllFuncs.pfnGetUserEntity( index );
	}
	ent = CL_GetEntityByIndex( BEAMENT_ENTITY( index ) );


	return ent;
}

void BeamNormalizeColor( BEAM *pBeam, float r, float g, float b, float brightness )
{
	float	_max, scale;

	_max = max( max( r, g ), b );

	if( _max == 0 )
	{
		pBeam->r = pBeam->g = pBeam->b = 255.0f;
		pBeam->brightness = brightness;
	}

	scale = 255.0f / _max;

	pBeam->r = r * scale;
	pBeam->g = g * scale;
	pBeam->b = b * scale;
	pBeam->brightness = ( brightness > 1.0f ) ? brightness : brightness * 255.0f;
}

static qboolean ComputeBeamEntPosition( int beamEnt, vec3_t_ref pt )
{
	cl_entity_t	*pEnt;
	int		nAttachment;

	pEnt = CL_GetBeamEntityByIndex( beamEnt );

	if( !pEnt )
	{
		VectorClear( pt );
		return false;
	}

	if( beamEnt < 0 )
		nAttachment = BEAMENT_ATTACHMENT( -beamEnt );
	else
		nAttachment = BEAMENT_ATTACHMENT( beamEnt );


	// get attachment
	if( nAttachment > 0 )
		VectorCopy( pEnt->attachment[nAttachment - 1], pt );
	else
	{
		if(( pEnt->index - 1 ) == cl.playernum && !cl.thirdperson )
		{
			// if we view beam at firstperson use viewmodel instead
			pEnt = &clgame.viewent;
		}
		VectorCopy( pEnt->origin, pt );
	}

	return true;
}

static void ComputeBeamPerpendicular( const vec3_t vecBeamDelta, vec3_t_ref pPerp )
{
	CrossProduct( RI.vforward, vecBeamDelta.Normalize(), pPerp );
	VectorNormalize( pPerp );
}

static void ComputeNormal( const vec3_t vStartPos, const vec3_t vNextPos, vec3_t_ref 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.
    pNormal = CrossProduct( vTangentY, vDirToBeam ).Normalize();
}

static void SetBeamRenderMode( int rendermode )
{
	if( rendermode == kRenderTransAdd )
	{
		pglEnable( GL_BLEND );
		pglBlendFunc( GL_SRC_ALPHA, GL_ONE );
	}
	else pglDisable( GL_BLEND );	// solid mode

	pglDisable( GL_ALPHA_TEST );
	pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
}

/*
================
CL_DrawSegsStretch
general code for drawing beams
================
*/
static void CL_DrawSegsStretch(int modelIndex, float frame, int rendermode, const vec3_t source, const vec3_t delta,
	float width, const vec3_t color)
{
	HSPRITE	m_hSprite;
	beamseg_t	curSeg, nextSeg;

	if (!cl_draw_beams->integer)
		return;

	m_hSprite = R_GetSpriteTexture(Mod_Handle(modelIndex), frame);

	if (!m_hSprite)
		return;

	curSeg.alpha = nextSeg.alpha = 1.0f;
	VectorCopy(color, curSeg.color);
	VectorCopy(color, nextSeg.color);
	VectorCopy(source, curSeg.pos);
	VectorAdd(source, delta, nextSeg.pos);

	SetBeamRenderMode(rendermode);
	GL_Bind(XASH_TEXTURE0, m_hSprite);
	pglBegin(GL_TRIANGLE_STRIP);

	// specify the next segment.
	curSeg.width = nextSeg.width = width * 2.0f;
	curSeg.texcoord = 0.0f;
	nextSeg.texcoord = 1.0f;

	// Get a vector that is perpendicular to us and perpendicular to the beam.
	// This is used to fatten the beam.
	vec3_t	vNormal, vAveNormal;
	ComputeNormal(curSeg.pos, nextSeg.pos, vNormal);
	VectorCopy(vNormal, vAveNormal);

	// draw regular segment
	vec3_t	vPoint1, vPoint2;
	VectorMA(curSeg.pos, (curSeg.width * 0.5f), vAveNormal, vPoint1);
	VectorMA(curSeg.pos, (-curSeg.width * 0.5f), vAveNormal, vPoint2);

	pglColor4f(curSeg.color[0], curSeg.color[1], curSeg.color[2], curSeg.alpha);
	pglTexCoord2f(0.0f, curSeg.texcoord);
	pglNormal3fv(vAveNormal.data());
	pglVertex3fv(vPoint1.data());

	pglColor4f(curSeg.color[0], curSeg.color[1], curSeg.color[2], curSeg.alpha);
	pglTexCoord2f(1.0f, curSeg.texcoord);
	pglNormal3fv(vAveNormal.data());
	pglVertex3fv(vPoint2.data());

	VectorMA(nextSeg.pos, (nextSeg.width * 0.5f), vAveNormal, vPoint1);
	VectorMA(nextSeg.pos, (-nextSeg.width * 0.5f), vAveNormal, vPoint2);

	pglColor4f(nextSeg.color[0], nextSeg.color[1], nextSeg.color[2], nextSeg.alpha);
	pglTexCoord2f(0.0f, nextSeg.texcoord);
	pglNormal3fv(vAveNormal.data());
	pglVertex3fv(vPoint1.data());

	pglColor4f(nextSeg.color[0], nextSeg.color[1], nextSeg.color[2], nextSeg.alpha);
	pglTexCoord2f(1.0f, nextSeg.texcoord);
	pglNormal3fv(vAveNormal.data());
	pglVertex3fv(vPoint2.data());

	pglEnd();
}

/*
================
CL_DrawSegs
general code for drawing beams
================
*/
static void CL_DrawSegs( int modelIndex, float frame, int rendermode, const vec3_t source, const vec3_t delta,
						 float width, float scale, float freq, float speed, int segments, int flags, const vec3_t color )
{
	int	noiseIndex, noiseStep;
	int	i, total_segs, segs_drawn;
	float	div, length, fraction, factor;
	float	flMaxWidth, vLast, vStep, brightness;
	vec3_t	perp1, vLastNormal;
	HSPRITE	m_hSprite;
	beamseg_t	curSeg;

	if( !cl_draw_beams->integer )
		return;

	m_hSprite = R_GetSpriteTexture( Mod_Handle( modelIndex ), frame );

	if( !m_hSprite || 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;
		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 *= 10;
		length = segments * ( 1.0f / 10 );
	}
	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( flags & FBEAM_SINENOISE )
	{
		noiseIndex = 0;
	}

	brightness = 1.0f;
	if( flags & FBEAM_SHADEIN )
	{
		brightness = 0;
	}

	// Choose two vectors that are perpendicular to the beam
	ComputeBeamPerpendicular( delta, perp1 );

	segs_drawn = 0;
	total_segs = segments;

	SetBeamRenderMode( rendermode );
	GL_Bind( XASH_TEXTURE0, m_hSprite );
	pglBegin( GL_TRIANGLE_STRIP );
#if CURVEDBEAM
	//	CurvedBeam Begin 1 of 3
	vec3_t end, mid, ang;
	vec3_t v_forward, v_right, v_up;

	VectorCopy(source, mid);
	VectorAdd(source, delta, end);
	if (RI.currentbeam) { //server beam
		if (RI.currentbeam->baseline.renderfx & FBEAM_CURVEDBEAM) {
			cl_entity_t* pEnt;
			pEnt = CL_GetBeamEntityByIndex(RI.currentbeam->curstate.owner);
			if (pEnt) {
				AngleVectors(pEnt->angles, v_forward, v_right, v_up);
				VectorMA(pEnt->origin, 120.0f, v_forward, mid);
			}
		}
	}
	else if (cl_active_beams) {  // client beam
		if (cl_active_beams->flags & FBEAM_CURVEDBEAM) {
			cl_entity_t* pEnt;
			pEnt = CL_GetBeamEntityByIndex(cl_active_beams->startEntity);
			if (pEnt) {
				AngleVectors(pEnt->angles, v_forward, v_right, v_up);
				VectorMA(pEnt->origin, 120.0f, v_forward, mid);
			}
		}
	}
	// CurvedBeam End 1 of 3
#endif // CURVEDBEAM
	// specify all the segments.
	for( i = 0; i < segments; i++ )
	{
		beamseg_t	nextSeg;
		vec3_t	vPoint1, vPoint2;

		ASSERT( noiseIndex < ( NOISE_DIVISIONS << 16 ));
		nextSeg.alpha = 1.0f;

		fraction = i * div;

		if(( flags & FBEAM_SHADEIN ) && ( flags & FBEAM_SHADEOUT ))
		{
			if( fraction < 0.5f )
			{
				brightness = 2.0f * fraction;
			}
			else
			{
				brightness = 2.0f * ( 1.0f - fraction );
			}
		}
		else if( flags & FBEAM_SHADEIN )
		{
			brightness = fraction;
		}
		else if( flags & FBEAM_SHADEOUT )
		{
			brightness = 1.0f - fraction;
		}

		// clamps
		brightness = bound( 0.0f, brightness, 1.0f );
		VectorScale( color, brightness, nextSeg.color );

		VectorMA( source, fraction, delta, nextSeg.pos );
#if CURVEDBEAM
		// CurvedBeam Begin 2 of 3
		if (RI.currentbeam) { //server beam
			if (RI.currentbeam->baseline.renderfx & FBEAM_CURVEDBEAM) {
				vec3_t p1, p2, p3, v1, v2, v3;
				VectorSubtract(mid, source, v1);
				VectorMA(source, fraction, v1, p1);
				VectorSubtract(end, mid, v2);
				VectorMA(mid, fraction, v2, p2);
				VectorSubtract(p2, p1, v3);
				VectorMA(p1, fraction, v3, nextSeg.pos);
			}
		}
		else if (cl_active_beams) { //client beam
			if (cl_active_beams->flags & FBEAM_CURVEDBEAM) {
				vec3_t p1, p2, p3, v1, v2, v3;
				VectorSubtract(mid, source, v1);
				VectorMA(source, fraction, v1, p1);
				VectorSubtract(end, mid, v2);
				VectorMA(mid, fraction, v2, p2);
				VectorSubtract(p2, p1, v3);
				VectorMA(p1, fraction, v3, nextSeg.pos);
			}
		}
		//CurvedBeam End 2 of 3
#endif
		// distort using noise
		if( scale != 0 )
		{
			factor = rgNoise[noiseIndex>>16] * scale;
			if( 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;

			ComputeNormal( curSeg.pos, nextSeg.pos, vNormal );

			if( segs_drawn > 1 )
			{
				// Average this with the previous normal
                vAveNormal = midpoint(vNormal, vLastNormal).Normalize();
			}
			else
			{
                vAveNormal = vNormal;
			}

			VectorCopy( vNormal, vLastNormal );

			// draw regular segment
			VectorMA( curSeg.pos, ( curSeg.width * 0.5f ), vAveNormal, vPoint1 );
			VectorMA( curSeg.pos, (-curSeg.width * 0.5f ), vAveNormal, vPoint2 );

			pglColor4f( curSeg.color[0], curSeg.color[1], curSeg.color[2], curSeg.alpha );
			pglTexCoord2f( 0.0f, curSeg.texcoord );
			pglNormal3fv( vAveNormal.data() );
			pglVertex3fv( vPoint1.data() );

			pglColor4f( curSeg.color[0], curSeg.color[1], curSeg.color[2], curSeg.alpha );
			pglTexCoord2f( 1.0f, curSeg.texcoord );
			pglNormal3fv( vAveNormal.data() );
			pglVertex3fv( vPoint2.data() );
		}

		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.
			pglColor4f( curSeg.color[0], curSeg.color[1], curSeg.color[2], curSeg.alpha );
			pglTexCoord2f( 0.0f, curSeg.texcoord );
			pglNormal3fv( vLastNormal.data() );
			pglVertex3fv( vPoint1.data() );

			pglColor4f( curSeg.color[0], curSeg.color[1], curSeg.color[2], curSeg.alpha );
			pglTexCoord2f( 1.0f, curSeg.texcoord );
			pglNormal3fv( vLastNormal.data() );
			pglVertex3fv( vPoint2.data() );
		}

		vLast += vStep; // Advance texture scroll (v axis only)
		noiseIndex += noiseStep;
	}

	pglEnd();
}

/*
================
CL_DrawDisk
Draw beamdisk
================
*/
static void CL_DrawDisk( int modelIndex, float frame, int rendermode, const vec3_t source, const vec3_t delta,
						 float width, float scale, float freq, float speed, int segments, const vec3_t color )
{
	float	div, length, fraction;
	float	w, vLast, vStep;
	HSPRITE	m_hSprite;
	vec3_t	point;
	int	i;

	m_hSprite = R_GetSpriteTexture( Mod_Handle( modelIndex ), frame );

	if( !m_hSprite || 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;

	w = freq * delta[2];

	SetBeamRenderMode( rendermode );
	GL_Bind( XASH_TEXTURE0, m_hSprite );

	pglBegin( GL_TRIANGLE_STRIP );

	// 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 );

		pglColor4f( color[0], color[1], color[2], 1.0f );
		pglTexCoord2f( 1.0f, vLast );
		pglVertex3fv( point.data() );

		SinCos( fraction * M_PI2, &s, &c );
		point[0] = s * w + source[0];
		point[1] = c * w + source[1];
		point[2] = source[2];

		pglColor4f( color[0], color[1], color[2], 1.0f );
		pglTexCoord2f( 0.0f, vLast );
		pglVertex3fv( point.data() );

		vLast += vStep;	// Advance texture scroll (v axis only)
	}

	pglEnd();
}

/*
================
CL_DrawCylinder
Draw beam cylinder
================
*/
static void CL_DrawCylinder( int modelIndex, float frame, int rendermode, const vec3_t source, const vec3_t delta,
							 float width, float scale, float freq, float speed, int segments, const vec3_t color )
{
	float	length, fraction;
	float	div, vLast, vStep;
	HSPRITE	m_hSprite;
	vec3_t	point;
	int	i;

	m_hSprite = R_GetSpriteTexture( Mod_Handle( modelIndex ), frame );

	if( !m_hSprite || 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.0f );
	scale = scale * length;

	GL_Cull( GL_NONE );	// draw both sides
	SetBeamRenderMode( rendermode );
	GL_Bind( XASH_TEXTURE0, m_hSprite );

	pglBegin( GL_TRIANGLE_STRIP );

	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;

		pglColor4f( 0.0f, 0.0f, 0.0f, 1.0f );
		pglTexCoord2f( 1.0f, vLast );
		pglVertex3fv( point.data() );

		point[0] = s * freq * (delta[2] + width) + source[0];
		point[1] = c * freq * (delta[2] + width) + source[1];
		point[2] = source[2] - width;

		pglColor4f( color[0], color[1], color[2], 1.0f );
		pglTexCoord2f( 0.0f, vLast );
		pglVertex3fv( point.data() );

		vLast += vStep;	// Advance texture scroll (v axis only)
	}

	pglEnd();
	GL_Cull( GL_FRONT );
}

/*
================
CL_DrawRing
Draw beamring
================
*/
void CL_DrawRing( int modelIndex, float frame, int rendermode, const vec3_t source, const vec3_t delta, float width,
				  float amplitude, float freq, float speed, int segments, const vec3_t color )
{
	int	i, j, noiseIndex, noiseStep;
	float	div, length, fraction, factor, vLast, vStep;
	vec3_t	last1, last2, point, screen, screenLast;
	vec3_t	center, xaxis, yaxis, zaxis, tmp, normal;
	float	radius, x, y, scale;
	HSPRITE	m_hSprite;
	vec3_t	d;

	m_hSprite = R_GetSpriteTexture( Mod_Handle( modelIndex ), frame );

	if( !m_hSprite || segments < 2 )
		return;

	VectorCopy( delta, d );

	VectorClear( screenLast );
	segments = segments * M_PI;

	if( segments > NOISE_DIVISIONS * 8 )
		segments = NOISE_DIVISIONS * 8;

	length = VectorLength( d ) * 0.01f * M_PI;
	if( length < 0.5f ) length = 0.5f;		// Don't lose all of the noise/texture on short beams

	div = 1.0 / ( 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)(( NOISE_DIVISIONS - 1 ) * div * 65536.0f ) * 8;
	noiseIndex = 0;

	VectorScale( d, 0.5f, d );
	VectorAdd( source, d, center );
	VectorClear( zaxis );

	VectorCopy( d, xaxis );
	radius = VectorLength( xaxis );

	// cull beamring
	// --------------------------------
	// Compute box center +/- radius
	last1[0] = radius;
	last1[1] = radius;
	last1[2] = scale;

	VectorAdd( center, last1, tmp );
	VectorSubtract( center, last1, screen );

	// Is that box in PVS && frustum?
	if( !Mod_BoxVisible( screen, tmp, Mod_GetCurrentVis( )) || R_CullBox( screen, tmp, RI.clipFlags ))
	{
		return;
	}

	yaxis[0] = xaxis[1];
	yaxis[1] = -xaxis[0];
	yaxis[2] = 0;

	VectorNormalize( yaxis );
	VectorScale( yaxis, radius, yaxis );

	j = segments / 8;

	SetBeamRenderMode( rendermode );
	GL_Bind( XASH_TEXTURE0, m_hSprite );

	pglBegin( GL_TRIANGLE_STRIP );

	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)
			pglColor4f( color[0], color[1], color[2], 1.0f );
			pglTexCoord2f( 1.0f, vLast );
			pglVertex3fv( last2.data() );

			pglColor4f( color[0], color[1], color[2], 1.0f );
			pglTexCoord2f( 0.0f, vLast );
			pglVertex3fv( last1.data() );
		}

		VectorCopy( screen, screenLast );
		noiseIndex += noiseStep;

		j--;
		if( j == 0 && amplitude != 0 )
		{
			j = segments / 8;
			FracNoise( rgNoise, NOISE_DIVISIONS, 1.0f );
		}
	}

	pglEnd();
}

/*
==============
CL_DrawLaser
Helper to drawing laser
==============
*/
void CL_DrawLaser( BEAM *pbeam, int frame, int rendermode, const vec3_t color, int spriteIndex )
{
	vec3_t	color2;
	vec3_t	beamDir;
	float	flDot;

	VectorCopy( color, color2 );

	VectorSubtract( pbeam->target, pbeam->source, beamDir );
	VectorNormalize( beamDir );

	flDot = DotProduct( beamDir, RI.vforward );

	// abort if the player's looking along it away from the source
	if( flDot > 0 )
	{
		return;
	}
	else
	{
		// Fade the beam if the player's not looking at the source
		float	flFade = pow( flDot, 10 );
		vec3_t	localDir, vecProjection, tmp;
		float	flDistance;

		// Fade the beam based on the player's proximity to the beam
		VectorSubtract( RI.vieworg, pbeam->source, localDir );
		flDot = DotProduct( beamDir, localDir );
		VectorScale( beamDir, 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;

		VectorScale( color2, flFade, color2 );
	}

	CL_DrawSegs( spriteIndex, frame, rendermode, pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude,
				 pbeam->freq, pbeam->speed, pbeam->segments, pbeam->flags, color2 );

}

/*
================
CL_DrawBeamFollow
Draw beam trail
================
*/
static void DrawBeamFollow( int modelIndex, particle_t *pHead, int frame, int rendermode, const vec3_t delta,
							vec3_t_ref screen, vec3_t_ref screenLast, float die, const vec3_t source, int flags, float width,
							float amplitude, float freq, const vec3_t color )
{
	float	fraction;
	float	div;
	float	vLast = 0.0;
	float	vStep = 1.0;
	vec3_t	last1, last2, tmp, normal, scaledColor, saved_last2;
	HSPRITE	m_hSprite;
	rgb_t	nColor, saved_nColor;

	m_hSprite = R_GetSpriteTexture( Mod_Handle( modelIndex ), frame );
	if( !m_hSprite ) 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, width, normal, last1 );
	VectorMA( delta, -width, normal, last2 );

	div = 1.0f / amplitude;
	fraction = ( die - cl.time ) * div;

	VectorScale( color, fraction, scaledColor );
	nColor[0] = (byte)bound( 0, (int)(scaledColor[0] * 255.0f), 255 );
	nColor[1] = (byte)bound( 0, (int)(scaledColor[1] * 255.0f), 255 );
	nColor[2] = (byte)bound( 0, (int)(scaledColor[2] * 255.0f), 255 );

	SetBeamRenderMode( rendermode );
	GL_Bind( XASH_TEXTURE0, m_hSprite );

	pglBegin( GL_TRIANGLES );

	while( pHead )
	{
		pglColor4ub( nColor[0], nColor[1], nColor[2], 255 );
		pglTexCoord2f( 1.0f, 1.0f );
		pglVertex3fv( last2.data() );

		pglColor4ub( nColor[0], nColor[1], nColor[2], 255 );
		pglTexCoord2f( 0.0f, 1.0f );
		pglVertex3fv( last1.data() );

		VectorCopy( last2, saved_last2 );
		saved_nColor[0] = nColor[0];
		saved_nColor[1] = nColor[1];
		saved_nColor[2] = nColor[2];

		if (vLast == 0.0f) {
			// Build world-space normal to screen-space direction vector
			VectorSubtract(screen, screenLast, tmp);

			// Transform point into screen space
			TriWorldToScreen(pHead->org, screen);
		}
		else {
			// Transform point into screen space
			TriWorldToScreen(pHead->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 );

		// 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( pHead->org,  width, normal, last1 );
		VectorMA( pHead->org, -width, normal, last2 );

		vLast += vStep;	// Advance texture scroll (v axis only)

		if( pHead->next != NULL )
		{
			fraction = (pHead->die - cl.time ) * div;
			VectorScale( color, fraction, scaledColor );
			nColor[0] = (byte)bound( 0, (int)(scaledColor[0] * 255.0f), 255 );
			nColor[1] = (byte)bound( 0, (int)(scaledColor[1] * 255.0f), 255 );
			nColor[2] = (byte)bound( 0, (int)(scaledColor[2] * 255.0f), 255 );
		}
		else
		{
            nColor[0] = nColor[1] = nColor[2] = 0;
			fraction = 0.0;
		}

		pglColor4ub( nColor[0], nColor[1], nColor[2], 255 );
		pglTexCoord2f( 0.0f, 0.0f );
		pglVertex3fv( last1.data() );

		pglColor4ub( saved_nColor[0], saved_nColor[1], saved_nColor[2], 255 );
		pglTexCoord2f( 1.0f, 1.0f );
		pglVertex3fv( saved_last2.data() );

		pglColor4ub( nColor[0], nColor[1], nColor[2], 255 );
		pglTexCoord2f( 0.0f, 0.0f );
		pglVertex3fv( last1.data() );

		pglColor4ub( nColor[0], nColor[1], nColor[2], 255 );
		pglTexCoord2f( 1.0f, 0.0f );
		pglVertex3fv( last2.data() );

		VectorCopy( screen, screenLast );

		pHead = pHead->next;
	}

	pglEnd();
}

/*
==============================================================
VIEWBEAMS MANAGEMENT
==============================================================
*/
#define BEAM_TRAILS		16	// 128 beams * 16 = 2048 particles
#define MAX_SERVERBEAMS	1024	// should be enough

BEAM		*cl_active_beams;
BEAM		*cl_free_beams;
BEAM		*cl_viewbeams = NULL;		// beams pool

particle_t	*cl_free_trails;
particle_t	*cl_beamtrails = NULL;		// trail partilces

cl_entity_t	*cl_custombeams[MAX_SERVERBEAMS];	// to avoid check of all the ents
/*
================
CL_InitViewBeams
================
*/
void CL_InitViewBeams( void )
{
	cl_viewbeams = (BEAM*)Mem_AlignedAlloc(cls.mempool, sizeof(BEAM) * GI->max_beams, alignof(BEAM));
	cl_beamtrails = (particle_t*)Mem_AlignedAlloc(cls.mempool, sizeof(particle_t) * GI->max_beams * BEAM_TRAILS, alignof(particle_t));
	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;

	// also clear any particles used by beams
	cl_free_trails = cl_beamtrails;

	for( i = 0; i < ( GI->max_beams * BEAM_TRAILS ) - 1; i++ )
		cl_beamtrails[i].next = &cl_beamtrails[i+1];
	cl_beamtrails[( GI->max_beams * BEAM_TRAILS ) - 1].next = NULL;

	cl.num_custombeams = 0;	// clear custom beams
}

/*
================
CL_FreeViewBeams
================
*/
void CL_FreeViewBeams( void )
{
	if( cl_viewbeams ) Mem_Free( cl_viewbeams );
	if( cl_beamtrails ) Mem_Free( cl_beamtrails );

	cl_viewbeams = NULL;
	cl_beamtrails = NULL;
}

/*
================
CL_AddCustomBeam
Add the beam that encoded as custom entity
================
*/
void CL_AddCustomBeam( cl_entity_t *pEnvBeam )
{
	if( cl.num_custombeams >= MAX_SERVERBEAMS )
	{
		MsgDev( D_ERROR, "Too many static beams %d!\n", cl.num_custombeams );
		return;
	}

	if( pEnvBeam )
	{
		cl_custombeams[cl.num_custombeams] = pEnvBeam;
		cl.num_custombeams++;
	}
}

/*
==============
CL_FreeDeadTrails
Free dead trails associated with beam
==============
*/
void CL_FreeDeadTrails( particle_t **trail )
{
	particle_t	*kill;
	particle_t	*p;

	// kill all the ones hanging direcly off the base pointer
	while( 1 )
	{
		kill = *trail;
		if( kill && kill->die < cl.time )
		{
			*trail = kill->next;
			kill->next = cl_free_trails;
			cl_free_trails = kill;
			continue;
		}
		break;
	}

	// kill off all the others
	for( p = *trail; p; p = p->next )
	{
		while( 1 )
		{
			kill = p->next;
			if( kill && kill->die < cl.time )
			{
				p->next = kill->next;
				kill->next = cl_free_trails;
				cl_free_trails = kill;
				continue;
			}
			break;
		}
	}
}

/*
==============
CL_AllocBeam
==============
*/
BEAM *CL_AllocBeam( void )
{
	BEAM	*pBeam;

	if( !cl_free_beams )
		return NULL;

	pBeam = cl_free_beams;
	cl_free_beams = pBeam->next;

	Q_memset( pBeam, 0, sizeof( *pBeam ));

	pBeam->next = cl_active_beams;
	cl_active_beams = pBeam;

	pBeam->die = cl.time;

	return pBeam;
}

/*
==============
CL_FreeBeam
==============
*/
void CL_FreeBeam( BEAM *pBeam )
{
	// free particles that have died off.
	CL_FreeDeadTrails( &pBeam->particles );

	// now link into free list;
	pBeam->next = cl_free_beams;
	cl_free_beams = pBeam;
}

/*
==============
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( CL_GetBeamEntityByIndex( 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
			CL_FreeBeam( 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_CullBeam
Cull the beam by bbox
==============
*/
qboolean CL_CullBeam( const vec3_t start, const vec3_t end, qboolean pvsOnly )
{
	vec3_t	mins, maxs;
	int	i;

	// support for custom mirror management
	if( RI.currentbeam != NULL )
	{
		// don't reflect this entity in mirrors
		if( RI.currentbeam->curstate.effects & EF_NOREFLECT && RI.params & RP_MIRRORVIEW )
			return true;

		// draw only in mirrors
		if( RI.currentbeam->curstate.effects & EF_REFLECTONLY && !( RI.params & RP_MIRRORVIEW ))
			return true;
	}

	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, RI.clipFlags ))
		{
			// beam is visible
			return false;
		}
	}

	// beam is culled
	return true;
}

/*
==============
CL_RecomputeBeamEndpoints
Recomputes beam endpoints..
==============
*/
qboolean CL_RecomputeBeamEndpoints( BEAM *pbeam )
{
	if( pbeam->flags & FBEAM_STARTENTITY )
	{
		if( ComputeBeamEntPosition( pbeam->startEntity, pbeam->source ))
		{
			pbeam->flags |= FBEAM_STARTVISIBLE;
#if CURVEDBEAM	
			//CurvedBeam Begin 3 of 3 by rentro
			if (RI.currentbeam) {   //server beam
				if (pbeam->flags & FBEAM_ENDENTITY) {
					RI.currentbeam->curstate.owner = pbeam->startEntity;
					RI.currentbeam->baseline.renderfx |= FBEAM_CURVEDBEAM;  //#defined as 0x00000200
				}
			}
			else {  // client beam
				if (pbeam->flags & FBEAM_ENDENTITY) {
					if(BEAMENT_ATTACHMENT(pbeam->startEntity))
						pbeam->flags |= FBEAM_CURVEDBEAM;
				}
			}
			//CurvedBeam End 3 of 3
#endif
		}
		else if( !( pbeam->flags & FBEAM_FOREVER ))
		{
			pbeam->flags &= ~(FBEAM_STARTENTITY);
		}
		else
		{
			// MsgDev( D_WARN, "can't find start entity\n" );
			// return false;
		}

		// if we've never seen the start entity, don't display
		if( !( pbeam->flags & FBEAM_STARTVISIBLE ))
			return false;
	}

	if( pbeam->flags & FBEAM_ENDENTITY )
	{
		if( ComputeBeamEntPosition( pbeam->endEntity, pbeam->target ))
		{
			pbeam->flags |= FBEAM_ENDVISIBLE;
		}
		else if( !( pbeam->flags & FBEAM_FOREVER ))
		{
			pbeam->flags &= ~(FBEAM_ENDENTITY);
			pbeam->die = cl.time;
			return false;
		}
		else
		{
			return false;
		}

		// if we've never seen the end entity, don't display
		if( !( pbeam->flags & FBEAM_ENDVISIBLE ))
			return false;
	}
	return true;
}

/*
==============
CL_BeamAttemptToDie
Check for expired beams
==============
*/
qboolean CL_BeamAttemptToDie( BEAM *pBeam )
{
	ASSERT( pBeam != NULL );

	// premanent beams never die automatically
	if( 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_UpdateBeam
Update beam vars
==============
*/
void CL_UpdateBeam( BEAM *pbeam, float frametime )
{
	pbeam->flags |= FBEAM_ISACTIVE;

	if( Mod_GetType( pbeam->modelIndex ) == mod_bad )
	{
		pbeam->flags &= ~FBEAM_ISACTIVE; // force to ignore
		pbeam->die = cl.time;
		return;
	}

	// update frequency
	pbeam->freq += frametime;

	// Generate fractal noise
	if( CL_IsInGame() && !cl.refdef.paused )
	{
		rgNoise[0] = 0;
		rgNoise[NOISE_DIVISIONS] = 0;
	}

	if( pbeam->amplitude != 0 && CL_IsInGame() && !cl.refdef.paused )
	{
		if( pbeam->flags & FBEAM_SINENOISE )
		{
			SineNoise( rgNoise, NOISE_DIVISIONS );
		}
		else
		{
			FracNoise( rgNoise, NOISE_DIVISIONS, 1.0f );
		}
	}

	// update end points
	if( pbeam->flags & ( FBEAM_STARTENTITY|FBEAM_ENDENTITY ))
	{
		// makes sure attachment[0] + attachment[1] are valid
		if( !CL_RecomputeBeamEndpoints( pbeam ))
		{
			pbeam->flags &= ~FBEAM_ISACTIVE; // force to ignore
			return;
		}

		// compute segments from the new endpoints
		VectorSubtract( pbeam->target, pbeam->source, 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
	}

	// get position data for spline beam
	switch( pbeam->type )
	{
		case TE_BEAMPOINTS:
		case TE_BEAMPOINTS_STRETCH:
			if( CL_CullBeam( pbeam->source, pbeam->target, false ))
			{
				pbeam->flags &= ~FBEAM_ISACTIVE; // force to ignore
				return;
			}
			break;
		case TE_BEAMPOINTS_TRACER:
		{
			float timepassd = (1.0 - ((pbeam->die - float(cl.time)) / pbeam->extra));	//life
			vec3_t vecSrc = pbeam->source + timepassd * pbeam->delta;
			vec3_t vecEnd = vecSrc + pbeam->delta.Normalize() * pbeam->extra2;	//length

			VectorCopy(vecSrc, pbeam->source);
			VectorCopy(vecEnd, pbeam->target);
		}
	}

	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;
	}
}

/*
==============
CL_DrawBeamFollow
Helper to drawing beam follow
==============
*/
void CL_DrawBeamFollow( int spriteIndex, BEAM *pbeam, int frame, int rendermode, float frametime, const vec3_t color )
{
	particle_t	*particles;
	particle_t	*pnew;
	float		div;
	vec3_t		delta;
	vec3_t		screenLast;
	vec3_t		screen;

	CL_FreeDeadTrails( &pbeam->particles );

	particles = pbeam->particles;
	pnew = NULL;

	div = 0;
	if( pbeam->flags & FBEAM_STARTENTITY )
	{
		if( particles )
		{
			VectorSubtract( particles->org, pbeam->source, delta );
			div = VectorLength( delta );

			if( div >= 32 && cl_free_trails )
			{
				pnew = cl_free_trails;
				cl_free_trails = pnew->next;
			}
		}
		else if( cl_free_trails )
		{
			pnew = cl_free_trails;
			cl_free_trails = 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;
	}

	// draw it
	DrawBeamFollow( spriteIndex, pbeam->particles, frame, rendermode, delta, screen, screenLast,
					pbeam->die, pbeam->source, pbeam->flags, pbeam->width,
					pbeam->amplitude, pbeam->freq, color );

	// drift popcorn trail if there is a velocity
	particles = pbeam->particles;

	while( particles )
	{
		VectorMA( particles->org, frametime, particles->vel, particles->org );
		particles = particles->next;
	}
}
/*
==============
CL_UpdateFCtail
Update tail vars
==============
*/
void CL_UpdateFCtail(BEAM* pbeam, float frametime)
{
	pbeam->flags |= FBEAM_ISACTIVE;

	pbeam->amplitude -= frametime;

	if (pbeam->amplitude > 0.0f) {
		return;
	}
	pbeam->amplitude = pbeam->t;

	cl_entity_t* pEnt;
	pEnt = CL_GetBeamEntityByIndex(pbeam->startEntity);
	if (!pEnt) return;

	if (!VectorCompare(pEnt->baseline.origin, pEnt->origin)) //is moving?
	{
		CL_FancySpray(pEnt->origin, pbeam->endEntity, pbeam->b, pbeam->r, pbeam->g, pbeam->brightness, pbeam->modelIndex, pbeam->frame, pbeam->frameCount, pbeam->extra, pbeam->extra2, pbeam->frameRate, frametime, pbeam->segments);
		VectorCopy(pEnt->baseline.origin, pEnt->origin);
	}
	else {
		pbeam->die = 0.0f;
	}
}
/*
==============
CL_BeamParticle
Create beam following with entity
==============
*/
BEAM* CL_BeamParticle(int startEnt, int modelIndex1, int modelIndex2, int modelIndex3,float r, float g, float b, float scale, float freq, float fadetime, int count, int random, int speed,int iRandomColor)
{
	cl_entity_t* pStart;
	BEAM* pBeam;

	// need a valid model.
	if (Mod_GetType(modelIndex1) != mod_sprite)
		return NULL;
	if (Mod_GetType(modelIndex2) != mod_sprite)
		return NULL;
	if (Mod_GetType(modelIndex3) != mod_sprite)
		return NULL;

	pStart = CL_GetBeamEntityByIndex(startEnt);

	// don't start temporary beams out of the PVS
	if (!pStart || !pStart->curstate.modelindex)
		return NULL;

	pBeam = CL_AllocBeam();
	if (!pBeam) return NULL;

	pBeam->type = TE_BEAMFOLLOW;
	pBeam->flags = FBEAM_FANCYTAIL;
	pBeam->modelIndex = modelIndex1;
	pBeam->pFollowModel = Mod_Handle(modelIndex1);
	pBeam->startEntity = startEnt;


	pBeam->frame = modelIndex2;
	pBeam->frameCount = modelIndex3;
	pBeam->r = scale;
	pBeam->g = random;
	pBeam->b = fadetime;
	pBeam->t = freq;
	pBeam->endEntity = count;
	pBeam->brightness = speed;

	pBeam->extra = r;
	pBeam->extra2 = g;
	pBeam->frameRate = b;
	pBeam->segments = iRandomColor;
	pBeam->die = cl.time + 10.0; //
	pBeam->amplitude = 0.0f;	// partilces lifetime


	return pBeam;
}
/*
==============
CL_DrawBeam
General code to drawing all beam types
==============
*/
void CL_DrawBeam( BEAM *pbeam )
{
	int	frame, rendermode;
	vec3_t	color, srcColor;

	// don't draw really short or inactive beams
	if(!( pbeam->flags & FBEAM_ISACTIVE ) || VectorLength( pbeam->delta ) < 0.1f )
	{
		return;
	}

	if( Mod_GetType( pbeam->modelIndex ) == mod_bad )
	{
		// don't draw beams without models
		pbeam->die = cl.time;
		return;
	}

	frame = ((int)( pbeam->frame + cl.time * pbeam->frameRate ) % pbeam->frameCount );
	rendermode = ( pbeam->flags & FBEAM_SOLID ) ? kRenderNormal : kRenderTransAdd;

	// set color
	VectorSet( srcColor, pbeam->r, pbeam->g, pbeam->b );

	if( pbeam->flags & FBEAM_FADEIN )
	{
		VectorScale( srcColor, pbeam->t, color );
	}
	else if ( pbeam->flags & FBEAM_FADEOUT )
	{
		VectorScale( srcColor, ( 1.0f - pbeam->t ), color );
	}
	else
	{
		VectorCopy( srcColor, color );
	}

	if( pbeam->type == TE_BEAMFOLLOW )
	{
		cl_entity_t	*pStart;

		// HACKHACK: get brightness from head entity
		pStart = CL_GetBeamEntityByIndex( pbeam->startEntity );
		if( pStart && pStart->curstate.rendermode != kRenderNormal )
			pbeam->brightness = pStart->curstate.renderamt;
	}

	VectorScale( color, ( pbeam->brightness / 255.0f ), color );
	VectorScale( color, ( 1.0f / 255.0f ), color );

	pglShadeModel( GL_SMOOTH );

	switch( pbeam->type )
	{
		case TE_BEAMDISK:
			CL_DrawDisk( pbeam->modelIndex, frame, rendermode, pbeam->source, pbeam->delta, pbeam->width,
						 pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments, color );
			break;
		case TE_BEAMTORUS:
		case TE_BEAMCYLINDER:
			CL_DrawCylinder( pbeam->modelIndex, frame, rendermode, pbeam->source, pbeam->delta, pbeam->width,
							 pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments, color );
			break;
		case TE_BEAMPOINTS_TRACER:
		case TE_BEAMPOINTS:
			CL_DrawSegs( pbeam->modelIndex, frame, rendermode, pbeam->source, pbeam->delta, pbeam->width,
						 pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments, pbeam->flags, color );
			break;
		case TE_BEAMFOLLOW:
			CL_DrawBeamFollow( pbeam->modelIndex, pbeam, frame, rendermode, cl.time - cl.oldtime, color );
			break;
		case TE_BEAMRING:
			CL_DrawRing( pbeam->modelIndex, frame, rendermode, pbeam->source, pbeam->delta, pbeam->width,
						 pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments, color );
			break;
		case TE_BEAMHOSE:
			CL_DrawLaser( pbeam, frame, rendermode, color, pbeam->modelIndex );
			break;
		case TE_BEAMPOINTS_STRETCH:
			CL_DrawSegsStretch(pbeam->modelIndex, frame, rendermode, pbeam->source, pbeam->delta, pbeam->width, color);
			break;
		default:
			MsgDev( D_ERROR, "CL_DrawBeam:  Unknown beam type %i\n", pbeam->type );
			break;
	}
	pglShadeModel( GL_FLAT );
}

/*
==============
CL_DrawCustomBeam
Draw beam from server
==============
*/
void CL_DrawCustomBeam( cl_entity_t *pbeam )
{
	BEAM	beam;
	int	beamType;
	int	beamFlags;

	ASSERT( pbeam != NULL );

	// bad texture ?
	if( Mod_GetType( pbeam->curstate.modelindex ) != mod_sprite )
		return;

	Q_memset( &beam, 0, sizeof( beam ));

	beamType = ( pbeam->curstate.rendermode & 0x0F );
	beamFlags = ( pbeam->curstate.rendermode & 0xF0 );
	beam.modelIndex = pbeam->curstate.modelindex;
	beam.frame = pbeam->curstate.frame;
	beam.frameRate = pbeam->curstate.framerate;
	beam.speed = pbeam->curstate.animtime;
	Mod_GetFrames( beam.modelIndex, &beam.frameCount );
	VectorCopy( pbeam->origin, beam.source );
	VectorCopy( pbeam->angles, beam.target );
	beam.freq	= cl.time * beam.speed;
	beam.startEntity = pbeam->curstate.sequence;
	beam.endEntity = pbeam->curstate.skin;
	beam.width = pbeam->curstate.scale;
	beam.amplitude = (float)(pbeam->curstate.body * 0.1f);
	beam.brightness = pbeam->curstate.renderamt;
	beam.r = pbeam->curstate.rendercolor.r;
	beam.g = pbeam->curstate.rendercolor.g;
	beam.b = pbeam->curstate.rendercolor.b;
	beam.flags = 0;

	VectorSubtract( beam.target, beam.source, beam.delta );

	if( beam.amplitude >= 0.50f )
		beam.segments = VectorLength( beam.delta ) * 0.25f + 3; // one per 4 pixels
	else beam.segments = VectorLength( beam.delta ) * 0.075f + 3; // one per 16 pixels

	// handle code from relinking.
	switch( beamType )
	{
		case BEAM_ENTS:
			beam.type	= TE_BEAMPOINTS;
			beam.flags = FBEAM_STARTENTITY|FBEAM_ENDENTITY;
			break;
		case BEAM_HOSE:
			beam.type	= TE_BEAMHOSE;
			beam.flags = FBEAM_STARTENTITY|FBEAM_ENDENTITY;
			break;
		case BEAM_ENTPOINT:
			beam.type	= TE_BEAMPOINTS;
			if( beam.startEntity ) beam.flags |= FBEAM_STARTENTITY;
			if( beam.endEntity ) beam.flags |= FBEAM_ENDENTITY;
			break;
		case BEAM_POINTS:
			// already set up
			break;
	}

	beam.flags |= beamFlags & ( FBEAM_SINENOISE|FBEAM_SOLID|FBEAM_SHADEIN|FBEAM_SHADEOUT );

	// draw it
	CL_UpdateBeam( &beam, cl.time - cl.oldtime );
	CL_DrawBeam( &beam );
}

void CL_DrawBeams( int fTrans )
{
	BEAM	*pBeam, *pNext;
	BEAM	*pPrev = NULL;
	int	i;

	// 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];

		if( fTrans && ((RI.currentbeam->curstate.rendermode & 0xF0) & FBEAM_SOLID ))
			continue;

		if( !fTrans && !((RI.currentbeam->curstate.rendermode & 0xF0) & FBEAM_SOLID ))
			continue;

		CL_DrawCustomBeam( RI.currentbeam );
		r_stats.c_view_beams_count++;
	}

	RI.currentbeam = NULL;

	if( !cl_active_beams )
		return;

	// 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 && pBeam->flags & FBEAM_SOLID )
			continue;
		if( !fTrans && !( 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
			CL_FreeBeam( pBeam );

			pBeam = NULL;
			continue;
		}
		// update tail state
		if (pBeam->flags & FBEAM_FANCYTAIL) {
			r_stats.c_view_beams_count++;
			CL_UpdateFCtail(pBeam, cl.time - cl.oldtime);

			pPrev = pBeam;
			continue;
		}

		// update beam state
		CL_UpdateBeam( pBeam, cl.time - cl.oldtime );
		r_stats.c_view_beams_count++;
		CL_DrawBeam( pBeam );

		pPrev = pBeam;
	}
}

/*
==============
CL_BeamKill
Remove beam attached to specified entity
and all particle trails (if this is a beamfollow)
==============
*/
void GAME_EXPORT CL_BeamKill( int deadEntity )
{
	cl_entity_t	*pDeadEntity;

	pDeadEntity = CL_GetBeamEntityByIndex( deadEntity );
	if( !pDeadEntity ) return;

	CL_KillDeadBeams( pDeadEntity );
}

/*
==============
CL_BeamEnts
Create beam between two ents
==============
*/
BEAM *GAME_EXPORT CL_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	*pStart, *pEnd;
	BEAM		*pBeam;

	// need a valid model.
	if( Mod_GetType( modelIndex ) != mod_sprite )
		return NULL;

	pStart = CL_GetBeamEntityByIndex( startEnt );
	pEnd = CL_GetBeamEntityByIndex( endEnt );

	// don't start temporary beams out of the PVS
	if( life != 0 && ( !pStart || !pStart->curstate.modelindex || !pEnd || !pEnd->curstate.modelindex ))
		return NULL;

	pBeam = CL_AllocBeam();
	if( !pBeam ) return NULL;

	pBeam->type = TE_BEAMPOINTS;
	pBeam->modelIndex = modelIndex;
	pBeam->startEntity = startEnt;
	pBeam->endEntity = endEnt;
	pBeam->frame = startFrame;
	pBeam->frameRate = framerate;
	Mod_GetFrames( modelIndex, &pBeam->frameCount );
	pBeam->freq = cl.time * speed;
	pBeam->flags = FBEAM_STARTENTITY|FBEAM_ENDENTITY;
	if( life == 0.0f ) pBeam->flags |= FBEAM_FOREVER;
	pBeam->die += life;
	pBeam->width = width;
	pBeam->amplitude = amplitude * 10;
	pBeam->speed = speed;
	BeamNormalizeColor( pBeam, r, g, b, brightness );

	CL_UpdateBeam( pBeam, 0.0f );

	return pBeam;
}

/*
==============
CL_BeamPoints
Create beam between two points
==============
*/
BEAM *GAME_EXPORT CL_BeamPoints( const vec3_t start, const 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;

	// need a valid model.
	if( Mod_GetType( modelIndex ) != mod_sprite )
		return NULL;

	// don't start temporary beams out of the PVS
	if( life != 0.0f && CL_CullBeam( start, end, true ))
		return NULL;

	pBeam = CL_AllocBeam();
	if( !pBeam ) return NULL;

	pBeam->type = TE_BEAMPOINTS;
	pBeam->modelIndex = modelIndex;
	VectorCopy( start, pBeam->source );
	VectorCopy( end, pBeam->target );
	pBeam->frame = startFrame;
	pBeam->frameRate = framerate;
	Mod_GetFrames( modelIndex, &pBeam->frameCount );
	pBeam->freq = cl.time * speed;
	if( life == 0.0f ) pBeam->flags |= FBEAM_FOREVER;
	pBeam->die += life;
	pBeam->width = width;
	pBeam->amplitude = amplitude;
	pBeam->speed = speed;
	BeamNormalizeColor( pBeam, r, g, b, brightness );

	VectorSubtract( pBeam->target, pBeam->source, pBeam->delta );

	if( pBeam->amplitude >= 0.50f )
		pBeam->segments = VectorLength( pBeam->delta ) * 0.25f + 3; // one per 4 pixels
	else pBeam->segments = VectorLength( pBeam->delta ) * 0.075f + 3; // one per 16 pixels

	CL_UpdateBeam( pBeam, 0.0f );

	return pBeam;
}

/*
==============
CL_BeamPoints_Stretch
Create beam between two points
==============
*/
BEAM* GAME_EXPORT CL_BeamPoints_Stretch(const vec3_t start, const vec3_t end, int modelIndex, float life, float width,
	float brightness, int startFrame, float framerate, float r, float g, float b)
{
	BEAM* pBeam;

	// need a valid model.
	if (Mod_GetType(modelIndex) != mod_sprite)
		return NULL;

	// don't start temporary beams out of the PVS
	if (life != 0.0f && CL_CullBeam(start, end, true))
		return NULL;

	pBeam = CL_AllocBeam();
	if (!pBeam) return NULL;

	pBeam->type = TE_BEAMPOINTS_STRETCH;
	pBeam->modelIndex = modelIndex;
	VectorCopy(start, pBeam->source);
	VectorCopy(end, pBeam->target);
	pBeam->frame = startFrame;
	pBeam->frameRate = framerate;
	Mod_GetFrames(modelIndex, &pBeam->frameCount);
	if (life == 0.0f) pBeam->flags |= FBEAM_FOREVER;
	pBeam->die += life;
	pBeam->width = width;
	BeamNormalizeColor(pBeam, r, g, b, brightness);

	VectorSubtract(pBeam->target, pBeam->source, pBeam->delta);

	CL_UpdateBeam(pBeam, 0.0f);

	return pBeam;
}


/*
==============
CL_BeamPoints_Tracer
Create beam between two points
==============
*/
BEAM* GAME_EXPORT CL_BeamPoints_Tracer(const vec3_t start, const vec3_t end, int modelIndex, float life, float width, float length, float amplitude,
	float brightness, float speed, int startFrame, float framerate, float r, float g, float b)
{
	BEAM* pBeam;

	// need a valid model.
	if (Mod_GetType(modelIndex) != mod_sprite)
		return NULL;

	// don't start temporary beams out of the PVS
	if (life != 0.0f && CL_CullBeam(start, end, true))
		return NULL;

	pBeam = CL_AllocBeam();
	if (!pBeam) return NULL;

	pBeam->type = TE_BEAMPOINTS_TRACER;
	pBeam->modelIndex = modelIndex;
	VectorCopy(start, pBeam->source);
	VectorCopy(end, pBeam->target);
	pBeam->frame = startFrame;
	pBeam->frameRate = framerate;
	Mod_GetFrames(modelIndex, &pBeam->frameCount);
	pBeam->freq = cl.time * speed;
	pBeam->extra = life;
	pBeam->extra2 = length;
	if (life == 0.0f) pBeam->flags |= FBEAM_FOREVER;

	pBeam->die += life;
	pBeam->width = width;
	pBeam->amplitude = amplitude;
	pBeam->speed = speed;
	BeamNormalizeColor(pBeam, r, g, b, brightness);

	VectorSubtract(pBeam->target, pBeam->source, pBeam->delta);

	if (pBeam->amplitude >= 0.50f)
		pBeam->segments = VectorLength(pBeam->delta) * 0.25f + 3; // one per 4 pixels
	else pBeam->segments = VectorLength(pBeam->delta) * 0.075f + 3; // one per 16 pixels

	CL_UpdateBeam(pBeam, 0.0f);

	return pBeam;
}


/*
==============
CL_BeamLighting
Create beam between two points (simple version)
==============
*/
BEAM *GAME_EXPORT CL_BeamLightning( const vec3_t start, const vec3_t end, int modelIndex, float life, float width, float amplitude,
									float brightness, float speed )
{
	return CL_BeamPoints( start, end, modelIndex, life, width, amplitude, brightness, speed,
						  0, 1.0f, 255.0f, 255.0f, 255.0f );
}

/*
==============
CL_BeamCirclePoints
Create beam cicrle
==============
*/
BEAM *GAME_EXPORT CL_BeamCirclePoints( int type, const vec3_t start, const 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;

	// need a valid model.
	if( Mod_GetType( modelIndex ) != mod_sprite )
		return NULL;

	pBeam = CL_AllocBeam();
	if( !pBeam ) return NULL;

	pBeam->type = type;
	pBeam->modelIndex = modelIndex;
	VectorCopy( start, pBeam->source );
	VectorCopy( end, pBeam->target );
	pBeam->frame = startFrame;
	pBeam->frameRate = framerate;
	Mod_GetFrames( modelIndex, &pBeam->frameCount );
	pBeam->freq = cl.time * speed;
	if( life == 0.0f ) pBeam->flags |= FBEAM_FOREVER;
	pBeam->die += life;
	pBeam->width = width;
	pBeam->amplitude = amplitude;
	pBeam->speed = speed;
	BeamNormalizeColor( pBeam, r, g, b, brightness );

	VectorSubtract( pBeam->target, pBeam->source, pBeam->delta );

	if( pBeam->amplitude >= 0.50f )
		pBeam->segments = VectorLength( pBeam->delta ) * 0.25f + 3; // one per 4 pixels
	else pBeam->segments = VectorLength( pBeam->delta ) * 0.075f + 3; // one per 16 pixels

	CL_UpdateBeam( pBeam, 0.0f );

	return pBeam;
}


/*
==============
CL_BeamEntPoint
Create beam between entity and point
==============
*/
BEAM *GAME_EXPORT CL_BeamEntPoint( int startEnt, const 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 )
{
	cl_entity_t	*pStart;
	BEAM		*pBeam;

	// need a valid model.
	if( Mod_GetType( modelIndex ) != mod_sprite )
		return NULL;

	pStart = CL_GetBeamEntityByIndex( startEnt );

	// don't start temporary beams out of the PVS
	if( life != 0.0f && ( !pStart || !pStart->curstate.modelindex ))
		return NULL;

	pBeam = CL_AllocBeam();
	if( !pBeam ) return NULL;

	pBeam->type = TE_BEAMPOINTS;
	pBeam->flags = FBEAM_STARTENTITY;
	pBeam->modelIndex = modelIndex;
	pBeam->pFollowModel = Mod_Handle( modelIndex );
	pBeam->startEntity = startEnt;
	VectorCopy( end, pBeam->target );
	pBeam->frame = startFrame;
	pBeam->frameRate = framerate;
	Mod_GetFrames( modelIndex, &pBeam->frameCount );
	pBeam->freq = cl.time * speed;
	if( life == 0.0f ) pBeam->flags |= FBEAM_FOREVER;
	pBeam->die += life;
	pBeam->width = width;
	pBeam->amplitude = amplitude * 10;
	pBeam->speed = speed;
	BeamNormalizeColor( pBeam, r, g, b, brightness );

	VectorSubtract( pBeam->target, pBeam->source, pBeam->delta );

	if( pBeam->amplitude >= 0.50f )
		pBeam->segments = VectorLength( pBeam->delta ) * 0.25f + 3; // one per 4 pixels
	else pBeam->segments = VectorLength( pBeam->delta ) * 0.075f + 3; // one per 16 pixels

	CL_UpdateBeam( pBeam, 0.0f );

	return pBeam;
}

/*
==============
CL_BeamRing
Create beam between two ents
==============
*/
BEAM *GAME_EXPORT CL_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 )
{
	cl_entity_t	*pStart, *pEnd;
	BEAM		*pBeam;

	// need a valid model.
	if( Mod_GetType( modelIndex ) != mod_sprite )
		return NULL;

	pStart = CL_GetBeamEntityByIndex( startEnt );
	pEnd = CL_GetBeamEntityByIndex( endEnt );

	// don't start temporary beams out of the PVS
	if( life != 0 && ( !pStart || !pStart->curstate.modelindex || !pEnd || !pEnd->curstate.modelindex ))
		return NULL;

	pBeam = CL_AllocBeam();
	if( !pBeam ) return NULL;

	pBeam->type = TE_BEAMRING;
	pBeam->modelIndex = modelIndex;
	pBeam->startEntity = startEnt;
	pBeam->endEntity = endEnt;
	pBeam->frame = startFrame;
	pBeam->frameRate = framerate;
	Mod_GetFrames( modelIndex, &pBeam->frameCount );
	pBeam->freq = cl.time * speed;
	pBeam->flags = FBEAM_STARTENTITY|FBEAM_ENDENTITY;
	if( life == 0.0f ) pBeam->flags |= FBEAM_FOREVER;
	pBeam->die += life;
	pBeam->width = width;
	pBeam->amplitude = amplitude;
	pBeam->speed = speed;
	BeamNormalizeColor( pBeam, r, g, b, brightness );

	CL_UpdateBeam( pBeam, 0.0f );

	return pBeam;
}

/*
==============
CL_BeamFollow
Create beam following with entity
==============
*/
BEAM *GAME_EXPORT CL_BeamFollow( int startEnt, int modelIndex, float life, float width, float r, float g, float b, float bright )
{
	cl_entity_t	*pStart;
	BEAM		*pBeam;

	// need a valid model.
	if( Mod_GetType( modelIndex ) != mod_sprite )
		return NULL;

	pStart = CL_GetBeamEntityByIndex( startEnt );

	// don't start temporary beams out of the PVS
	if( life != 0.0f && ( !pStart || !pStart->curstate.modelindex ))
		return NULL;

	pBeam = CL_AllocBeam();
	if( !pBeam ) return NULL;

	pBeam->type = TE_BEAMFOLLOW;
	pBeam->flags = FBEAM_STARTENTITY;
	pBeam->modelIndex = modelIndex;
	pBeam->pFollowModel = Mod_Handle( modelIndex );
	pBeam->startEntity = startEnt;
	pBeam->frame = 0;
	pBeam->frameRate = 1.0f;
	Mod_GetFrames( modelIndex, &pBeam->frameCount );
	if( life == 0.0f ) pBeam->flags |= FBEAM_FOREVER;
	pBeam->freq = cl.time;
	pBeam->die += life;
	pBeam->width = width;
	pBeam->amplitude = life;	// partilces lifetime
	pBeam->speed = 1.0f;
	BeamNormalizeColor( pBeam, r, g, b, bright );

	CL_UpdateBeam( pBeam, 0.0f );

	return pBeam;
}

/*
==============
CL_BeamSprite
Create a beam with sprite at the end
Valve legacy
==============
*/
void CL_BeamSprite( const vec3_t start, const vec3_t end, int beamIndex, int spriteIndex )
{
	CL_BeamLightning( start, end, beamIndex, 0.1f, 1.0f, 0.0f, 255, 1.0f );
	CL_DefaultSprite( end, spriteIndex, 1.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;
	float	brightness, noise, speed;
	int	startEnt, endEnt;
	float	r, g, b;

	switch( beamType )
	{
		case TE_BEAMPOINTS:
			start[0] = BF_ReadCoord( msg );
			start[1] = BF_ReadCoord( msg );
			start[2] = BF_ReadCoord( msg );
			end[0] = BF_ReadCoord( msg );
			end[1] = BF_ReadCoord( msg );
			end[2] = BF_ReadCoord( msg );
			modelIndex = BF_ReadShort( msg );
			startFrame = BF_ReadByte( msg );
			frameRate = (float)BF_ReadByte( msg );
			life = (float)(BF_ReadByte( msg ) * 0.1f);
			width = (float)(BF_ReadByte( msg ) * 0.1f);
			noise = (float)(BF_ReadByte( msg ) * 0.1f);
			r = (float)BF_ReadByte( msg );
			g = (float)BF_ReadByte( msg );
			b = (float)BF_ReadByte( msg );
			brightness = (float)BF_ReadByte( msg );
			speed = (float)(BF_ReadByte( msg ) * 0.1f);
			CL_BeamPoints( start, end, modelIndex, life, width, noise, brightness, speed, startFrame,
						   frameRate, r, g, b );
			break;
		case TE_BEAMENTPOINT:
			startEnt = BF_ReadShort( msg );
			end[0] = BF_ReadCoord( msg );
			end[1] = BF_ReadCoord( msg );
			end[2] = BF_ReadCoord( msg );
			modelIndex = BF_ReadShort( msg );
			startFrame = BF_ReadByte( msg );
			frameRate = (float)BF_ReadByte( msg );
			life = (float)(BF_ReadByte( msg ) * 0.1f);
			width = (float)(BF_ReadByte( msg ) * 0.1f);
			noise = (float)(BF_ReadByte( msg ) * 0.01f);
			r = (float)BF_ReadByte( msg );
			g = (float)BF_ReadByte( msg );
			b = (float)BF_ReadByte( msg );
			brightness = (float)BF_ReadByte( msg );
			speed = (float)(BF_ReadByte( msg ) * 0.1f);
			CL_BeamEntPoint( startEnt, end, modelIndex, life, width, noise, brightness, speed, startFrame,
							 frameRate, r, g, b );
			break;
		case TE_LIGHTNING:
			start[0] = BF_ReadCoord( msg );
			start[1] = BF_ReadCoord( msg );
			start[2] = BF_ReadCoord( msg );
			end[0] = BF_ReadCoord( msg );
			end[1] = BF_ReadCoord( msg );
			end[2] = BF_ReadCoord( msg );
			life = (float)(BF_ReadByte( msg ) * 0.1f);
			width = (float)(BF_ReadByte( msg ) * 0.1f);
			noise = (float)(BF_ReadByte( msg ) * 0.1f);
			modelIndex = BF_ReadShort( msg );
			CL_BeamLightning( start, end, modelIndex, life, width, noise, 255.0f, 1.0f );
			break;
		case TE_BEAMENTS:
			startEnt = BF_ReadShort( msg );
			endEnt = BF_ReadShort( msg );
			modelIndex = BF_ReadShort( msg );
			startFrame = BF_ReadByte( msg );
			frameRate = (float)(BF_ReadByte( msg ) * 0.1f);
			life = (float)(BF_ReadByte( msg ) * 0.1f);
			width = (float)(BF_ReadByte( msg ) * 0.1f);
			noise = (float)(BF_ReadByte( msg ) * 0.01f);
			r = (float)BF_ReadByte( msg );
			g = (float)BF_ReadByte( msg );
			b = (float)BF_ReadByte( msg );
			brightness = (float)BF_ReadByte( msg );
			speed = (float)(BF_ReadByte( msg ) * 0.1f);
			CL_BeamEnts( startEnt, endEnt, modelIndex, life, width, noise, brightness, speed, startFrame,
						 frameRate, r, g, b );
			break;
		case TE_BEAM:
			MsgDev( D_ERROR, "TE_BEAM is obsolete\n" );
			break;
		case TE_BEAMSPRITE:
			start[0] = BF_ReadCoord( msg );
			start[1] = BF_ReadCoord( msg );
			start[2] = BF_ReadCoord( msg );
			end[0] = BF_ReadCoord( msg );
			end[1] = BF_ReadCoord( msg );
			end[2] = BF_ReadCoord( msg );
			modelIndex = BF_ReadShort( msg );	// beam model
			startFrame = BF_ReadShort( msg );	// sprite model
			CL_BeamSprite( start, end, modelIndex, startFrame );
			break;
		case TE_BEAMTORUS:
		case TE_BEAMDISK:
		case TE_BEAMCYLINDER:
			start[0] = BF_ReadCoord( msg );
			start[1] = BF_ReadCoord( msg );
			start[2] = BF_ReadCoord( msg );
			end[0] = BF_ReadCoord( msg );
			end[1] = BF_ReadCoord( msg );
			end[2] = BF_ReadCoord( msg );
			modelIndex = BF_ReadShort( msg );
			startFrame = BF_ReadByte( msg );
			frameRate = (float)(BF_ReadByte( msg ) * 0.1f);
			life = (float)(BF_ReadByte( msg ) * 0.1f);
			width = (float)BF_ReadByte( msg );
			noise = (float)(BF_ReadByte( msg ) * 0.1f);
			r = (float)BF_ReadByte( msg );
			g = (float)BF_ReadByte( msg );
			b = (float)BF_ReadByte( msg );
			brightness = (float)BF_ReadByte( msg );
			speed = (float)(BF_ReadByte( msg ) * 0.1f);
			CL_BeamCirclePoints( beamType, start, end, modelIndex, life, width, noise, brightness, speed,
								 startFrame, frameRate, r, g, b );
			break;
		case TE_BEAMFOLLOW:
			startEnt = BF_ReadShort( msg );
			modelIndex = BF_ReadShort( msg );
			life = (float)(BF_ReadByte( msg ) * 0.1f);
			width = (float)BF_ReadByte( msg );
			r = (float)BF_ReadByte( msg );
			g = (float)BF_ReadByte( msg );
			b = (float)BF_ReadByte( msg );
			brightness = (float)BF_ReadByte( msg );
			CL_BeamFollow( startEnt, modelIndex, life, width, r, g, b, brightness );
			break;
		case TE_BEAMRING:
			startEnt = BF_ReadShort( msg );
			endEnt = BF_ReadShort( msg );
			modelIndex = BF_ReadShort( msg );
			startFrame = BF_ReadByte( msg );
			frameRate = (float)BF_ReadByte( msg );
			life = (float)(BF_ReadByte( msg ) * 0.1f);
			width = (float)(BF_ReadByte( msg ) * 0.1f);
			noise = (float)(BF_ReadByte( msg ) * 0.1f);
			r = (float)BF_ReadByte( msg );
			g = (float)BF_ReadByte( msg );
			b = (float)BF_ReadByte( msg );
			brightness = (float)BF_ReadByte( msg );
			speed = (float)(BF_ReadByte( msg ) * 0.1f);
			CL_BeamRing( startEnt, endEnt, modelIndex, life, width, noise, brightness, speed, startFrame,
						 frameRate, r, g, b );
			break;
		case TE_BEAMHOSE:
			MsgDev( D_ERROR, "TE_BEAMHOSE is obsolete\n" );
			break;
		case TE_KILLBEAM:
			startEnt = BF_ReadShort( msg );
			CL_BeamKill( startEnt );
			break;
		case TE_BEAMPOINTS_STRETCH:
			start[0] = BF_ReadCoord(msg);
			start[1] = BF_ReadCoord(msg);
			start[2] = BF_ReadCoord(msg);
			end[0] = BF_ReadCoord(msg);
			end[1] = BF_ReadCoord(msg);
			end[2] = BF_ReadCoord(msg);
			modelIndex = BF_ReadShort(msg);
			startFrame = BF_ReadByte(msg);
			frameRate = (float)BF_ReadByte(msg);
			life = (float)(BF_ReadByte(msg) * 0.1f);
			width = (float)(BF_ReadByte(msg) * 0.1f);
			r = (float)BF_ReadByte(msg);
			g = (float)BF_ReadByte(msg);
			b = (float)BF_ReadByte(msg);
			brightness = (float)BF_ReadByte(msg);
			CL_BeamPoints_Stretch(start, end, modelIndex, life, width, brightness, startFrame, frameRate, r, g, b);
			break;

		case TE_BEAMPARTICLE:
		{
			startEnt = BF_ReadShort(msg);
			int modelIndex1 = BF_ReadShort(msg);
			int modelIndex2 = BF_ReadShort(msg);
			int modelIndex3 = BF_ReadShort(msg);
			float r = (float)BF_ReadByte(msg);
			float g = (float)BF_ReadByte(msg);
			float b = (float)BF_ReadByte(msg);
			int randomcolor = BF_ReadByte(msg);
			float scale = (float)(BF_ReadByte(msg) * 0.01f);
			float freq = (float)(BF_ReadByte(msg) * 0.01f);
			float fadetime = (float)BF_ReadByte(msg) * 0.1;
			int count = BF_ReadByte(msg);
			int random = BF_ReadByte(msg);
			int speed = (float)BF_ReadByte(msg);
			CL_BeamParticle(startEnt, modelIndex1, modelIndex2, modelIndex3, r, g, b, scale, freq, fadetime, count, random, speed, randomcolor);
			break;
		}
		case TE_CUSTOMBEAMPOINTS:
		{
			start[0] = BF_ReadCoord(msg);
			start[1] = BF_ReadCoord(msg);
			start[2] = BF_ReadCoord(msg);
			end[0] = BF_ReadCoord(msg);
			end[1] = BF_ReadCoord(msg);
			end[2] = BF_ReadCoord(msg);
			modelIndex = BF_ReadShort(msg);
			startFrame = BF_ReadByte(msg);
			frameRate = (float)BF_ReadByte(msg);
			life = (float)(BF_ReadByte(msg) * 0.1f);
			width = (float)(BF_ReadByte(msg) * 0.1f);
			noise = (float)(BF_ReadByte(msg) * 0.1f);
			r = (float)BF_ReadByte(msg);
			g = (float)BF_ReadByte(msg);
			b = (float)BF_ReadByte(msg);
			brightness = (float)BF_ReadByte(msg);
			speed = (float)(BF_ReadByte(msg) * 0.1f);
			int flags = BF_ReadByte(msg);
			BEAM* pBeam = CL_BeamPoints(start, end, modelIndex, life, width, noise, brightness, speed, startFrame,
				frameRate, r, g, b);

			if (pBeam)
				pBeam->flags |= flags;
			break;
		}
		case TE_BEAMPOINTS_TRACER:
		{
			start[0] = BF_ReadCoord(msg);
			start[1] = BF_ReadCoord(msg);
			start[2] = BF_ReadCoord(msg);
			end[0] = BF_ReadCoord(msg);
			end[1] = BF_ReadCoord(msg);
			end[2] = BF_ReadCoord(msg);
			modelIndex = BF_ReadShort(msg);
			startFrame = BF_ReadByte(msg);
			frameRate = (float)BF_ReadByte(msg);
			life = (float)(BF_ReadByte(msg) * 0.1f);
			width = (float)(BF_ReadByte(msg) * 0.1f);
			noise = (float)(BF_ReadByte(msg) * 0.1f);
			r = (float)BF_ReadByte(msg);
			g = (float)BF_ReadByte(msg);
			b = (float)BF_ReadByte(msg);
			brightness = (float)BF_ReadByte(msg);
			speed = (float)(BF_ReadByte(msg) * 0.1f);
			int flags = BF_ReadByte(msg);
			float length = BF_ReadShort(msg);
			BEAM* pBeam = CL_BeamPoints_Tracer(start, end, modelIndex, life, width, length, noise, brightness, speed, startFrame,
				frameRate, r, g, b);

			if (pBeam)
				pBeam->flags |= flags;
			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 = (char *)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( !CL_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 );
}

#endif // XASH_DEDICATED