704 lines
19 KiB
C++
704 lines
19 KiB
C++
//=======================================================================
|
|
// Copyright (C) Shambler Team 2005
|
|
// rain.cpp - TriAPI weather effects
|
|
// based on original code from BUzer
|
|
//=======================================================================
|
|
|
|
#include "extdll.h"
|
|
#include "utils.h"
|
|
#include "r_efx.h"
|
|
#include "triangle_api.h"
|
|
#include "hud.h"
|
|
#include "r_weather.h"
|
|
#include "pm_defs.h"
|
|
|
|
void WaterLandingEffect( cl_drip *drip );
|
|
void ParseRainFile( void );
|
|
|
|
cl_drip FirstChainDrip;
|
|
cl_rainfx FirstChainFX;
|
|
|
|
double rain_curtime; // current time
|
|
double rain_oldtime; // last time we have updated drips
|
|
double rain_timedelta; // difference between old time and current time
|
|
double rain_nextspawntime; // when the next drip should be spawned
|
|
cvar_t *cl_debugrain;
|
|
int dripcounter = 0;
|
|
int fxcounter = 0;
|
|
HSPRITE hsprWaterRing;
|
|
HSPRITE hsprDripTexture;
|
|
|
|
/*
|
|
=================================
|
|
ProcessRain
|
|
|
|
Must think every frame.
|
|
=================================
|
|
*/
|
|
void ProcessRain( void )
|
|
{
|
|
rain_oldtime = rain_curtime; // save old time
|
|
rain_curtime = GetClientTime();
|
|
rain_timedelta = rain_curtime - rain_oldtime;
|
|
|
|
// first frame
|
|
if ( rain_oldtime == 0 )
|
|
{
|
|
// fix first frame bug with nextspawntime
|
|
rain_nextspawntime = GetClientTime();
|
|
ParseRainFile();
|
|
return;
|
|
}
|
|
|
|
if ( gHUD.Rain.dripsPerSecond == 0 && FirstChainDrip.p_Next == NULL )
|
|
{
|
|
// keep nextspawntime correct
|
|
rain_nextspawntime = rain_curtime;
|
|
return;
|
|
}
|
|
|
|
if ( !IN_GAME() ) return; // not in pause
|
|
|
|
double timeBetweenDrips = 1 / (double)gHUD.Rain.dripsPerSecond;
|
|
|
|
cl_drip* curDrip = FirstChainDrip.p_Next;
|
|
cl_drip* nextDrip = NULL;
|
|
|
|
cl_entity_t *player = GetLocalPlayer();
|
|
|
|
if( !player ) return; // not in game
|
|
|
|
// save debug info
|
|
float debug_lifetime = 0;
|
|
int debug_howmany = 0;
|
|
int debug_attempted = 0;
|
|
int debug_dropped = 0;
|
|
|
|
while ( curDrip != NULL ) // go through list
|
|
{
|
|
nextDrip = curDrip->p_Next; // save pointer to next drip
|
|
|
|
if ( gHUD.Rain.weatherMode == 0 )
|
|
curDrip->origin.z -= rain_timedelta * DRIPSPEED; // rain
|
|
else curDrip->origin.z -= rain_timedelta * SNOWSPEED; // snow
|
|
|
|
curDrip->origin.x += rain_timedelta * curDrip->xDelta;
|
|
curDrip->origin.y += rain_timedelta * curDrip->yDelta;
|
|
|
|
// remove drip if its origin lower than minHeight
|
|
if ( curDrip->origin.z < curDrip->minHeight )
|
|
{
|
|
if ( curDrip->landInWater/* && gHUD.Rain.weatherMode == 0*/ )
|
|
WaterLandingEffect( curDrip ); // create water rings
|
|
|
|
if ( cl_debugrain->value )
|
|
{
|
|
debug_lifetime += (rain_curtime - curDrip->birthTime);
|
|
debug_howmany++;
|
|
}
|
|
|
|
curDrip->p_Prev->p_Next = curDrip->p_Next; // link chain
|
|
if ( nextDrip != NULL )
|
|
nextDrip->p_Prev = curDrip->p_Prev;
|
|
delete curDrip;
|
|
|
|
dripcounter--;
|
|
}
|
|
|
|
curDrip = nextDrip; // restore pointer, so we can continue moving through chain
|
|
}
|
|
|
|
int maxDelta; // maximum height randomize distance
|
|
float falltime;
|
|
if ( gHUD.Rain.weatherMode == 0 )
|
|
{
|
|
maxDelta = DRIPSPEED * rain_timedelta; // for rain
|
|
falltime = (gHUD.Rain.globalHeight + 4096) / DRIPSPEED;
|
|
}
|
|
else
|
|
{
|
|
maxDelta = SNOWSPEED * rain_timedelta; // for snow
|
|
falltime = (gHUD.Rain.globalHeight + 4096) / SNOWSPEED;
|
|
}
|
|
|
|
while ( rain_nextspawntime < rain_curtime )
|
|
{
|
|
rain_nextspawntime += timeBetweenDrips;
|
|
if ( cl_debugrain->value ) debug_attempted++;
|
|
|
|
if ( dripcounter < MAXDRIPS ) // check for overflow
|
|
{
|
|
float deathHeight;
|
|
Vector vecStart, vecEnd;
|
|
|
|
vecStart[0] = RANDOM_FLOAT( player->origin.x - gHUD.Rain.distFromPlayer, player->origin.x + gHUD.Rain.distFromPlayer );
|
|
vecStart[1] = RANDOM_FLOAT( player->origin.y - gHUD.Rain.distFromPlayer, player->origin.y + gHUD.Rain.distFromPlayer );
|
|
vecStart[2] = gHUD.Rain.globalHeight;
|
|
|
|
float xDelta = gHUD.Rain.windX + RANDOM_FLOAT( gHUD.Rain.randX * -1, gHUD.Rain.randX );
|
|
float yDelta = gHUD.Rain.windY + RANDOM_FLOAT( gHUD.Rain.randY * -1, gHUD.Rain.randY );
|
|
|
|
// find a point at bottom of map
|
|
vecEnd[0] = falltime * xDelta;
|
|
vecEnd[1] = falltime * yDelta;
|
|
vecEnd[2] = -4096;
|
|
|
|
pmtrace_t pmtrace;
|
|
gEngfuncs.pEventAPI->EV_SetTraceHull( 2 );
|
|
gEngfuncs.pEventAPI->EV_PlayerTrace( vecStart, vecStart + vecEnd, PM_STUDIO_IGNORE, -1, &pmtrace );
|
|
|
|
if ( pmtrace.startsolid || pmtrace.allsolid )
|
|
{
|
|
if ( cl_debugrain->value > 1 ) debug_dropped++;
|
|
continue; // drip cannot be placed
|
|
}
|
|
|
|
// falling to water?
|
|
int contents = POINT_CONTENTS( pmtrace.endpos );
|
|
|
|
if ( contents == CONTENTS_WATER )
|
|
{
|
|
// NOTE: in Xash3D PM_WaterEntity always return a valid water volume or NULL
|
|
// so not needs to run additional checks here
|
|
cl_entity_t *pWater = gEngfuncs.pfnWaterEntity( pmtrace.endpos );
|
|
if ( pWater )
|
|
{
|
|
deathHeight = pWater->curstate.maxs[2];
|
|
}
|
|
else
|
|
{
|
|
// hit the world
|
|
deathHeight = pmtrace.endpos[2];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
deathHeight = pmtrace.endpos[2];
|
|
}
|
|
|
|
// just in case..
|
|
if ( deathHeight > vecStart[2] )
|
|
{
|
|
gEngfuncs.Con_Printf( "rain: can't create drip in water\n" );
|
|
continue;
|
|
}
|
|
|
|
|
|
cl_drip *newClDrip = new cl_drip;
|
|
if ( !newClDrip )
|
|
{
|
|
gHUD.Rain.dripsPerSecond = 0; // disable rain
|
|
gEngfuncs.Con_Printf( "rain: failed to allocate object!\n");
|
|
return;
|
|
}
|
|
|
|
vecStart[2] -= RANDOM_FLOAT( 0, maxDelta ); // randomize a bit
|
|
|
|
newClDrip->alpha = RANDOM_FLOAT( 0.12, 0.2 );
|
|
newClDrip->origin = vecStart;
|
|
|
|
newClDrip->xDelta = xDelta;
|
|
newClDrip->yDelta = yDelta;
|
|
|
|
newClDrip->birthTime = rain_curtime; // store time when it was spawned
|
|
newClDrip->minHeight = deathHeight;
|
|
|
|
if ( contents == CONTENTS_WATER )
|
|
newClDrip->landInWater = 1;
|
|
else newClDrip->landInWater = 0;
|
|
|
|
// add to first place in chain
|
|
newClDrip->p_Next = FirstChainDrip.p_Next;
|
|
newClDrip->p_Prev = &FirstChainDrip;
|
|
if ( newClDrip->p_Next != NULL )
|
|
newClDrip->p_Next->p_Prev = newClDrip;
|
|
FirstChainDrip.p_Next = newClDrip;
|
|
|
|
dripcounter++;
|
|
}
|
|
else
|
|
{
|
|
if ( cl_debugrain->value )
|
|
gEngfuncs.Con_Printf( "rain: drip limit overflow! %i > %i\n", dripcounter, MAXDRIPS );
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( cl_debugrain->value ) // print debug info
|
|
{
|
|
gEngfuncs.Con_Printf( "Rain info: Drips exist: %i\n", dripcounter );
|
|
gEngfuncs.Con_Printf( "Rain info: FX's exist: %i\n", fxcounter );
|
|
gEngfuncs.Con_Printf( "Rain info: Attempted/Dropped: %i, %i\n", debug_attempted, debug_dropped );
|
|
if ( debug_howmany )
|
|
{
|
|
float ave = debug_lifetime / (float)debug_howmany;
|
|
gEngfuncs.Con_Printf( "Rain info: Average drip life time: %f\n", ave);
|
|
}
|
|
else gEngfuncs.Con_Printf( "Rain info: Average drip life time: --\n" );
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
=================================
|
|
WaterLandingEffect
|
|
=================================
|
|
*/
|
|
void WaterLandingEffect( cl_drip *drip )
|
|
{
|
|
if ( fxcounter >= MAXFX )
|
|
{
|
|
gEngfuncs.Con_Printf( "Rain error: FX limit overflow!\n" );
|
|
return;
|
|
}
|
|
|
|
cl_rainfx *newFX = new cl_rainfx;
|
|
if ( !newFX )
|
|
{
|
|
gEngfuncs.Con_Printf( "Rain error: failed to allocate FX object!\n");
|
|
return;
|
|
}
|
|
|
|
newFX->alpha = RANDOM_FLOAT( 0.6, 0.9 );
|
|
newFX->origin = drip->origin;
|
|
newFX->origin[2] = drip->minHeight; // correct position
|
|
|
|
newFX->birthTime = GetClientTime();
|
|
newFX->life = RANDOM_FLOAT( 0.7f, 1.0f );
|
|
|
|
// add to first place in chain
|
|
newFX->p_Next = FirstChainFX.p_Next;
|
|
newFX->p_Prev = &FirstChainFX;
|
|
if ( newFX->p_Next != NULL )
|
|
newFX->p_Next->p_Prev = newFX;
|
|
FirstChainFX.p_Next = newFX;
|
|
|
|
fxcounter++;
|
|
}
|
|
|
|
/*
|
|
=================================
|
|
ProcessFXObjects
|
|
|
|
Remove all fx objects with out time to live
|
|
Call every frame before ProcessRain
|
|
=================================
|
|
*/
|
|
void ProcessFXObjects( void )
|
|
{
|
|
float curtime = GetClientTime();
|
|
|
|
cl_rainfx* curFX = FirstChainFX.p_Next;
|
|
cl_rainfx* nextFX = NULL;
|
|
|
|
while ( curFX != NULL ) // go through FX objects list
|
|
{
|
|
nextFX = curFX->p_Next; // save pointer to next
|
|
|
|
// delete current?
|
|
if (( curFX->birthTime + curFX->life ) < curtime )
|
|
{
|
|
curFX->p_Prev->p_Next = curFX->p_Next; // link chain
|
|
if ( nextFX != NULL )
|
|
nextFX->p_Prev = curFX->p_Prev;
|
|
delete curFX;
|
|
fxcounter--;
|
|
}
|
|
curFX = nextFX; // restore pointer
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================================
|
|
ResetRain
|
|
clear memory, delete all objects
|
|
=================================
|
|
*/
|
|
void ResetRain( void )
|
|
{
|
|
// delete all drips
|
|
cl_drip* delDrip = FirstChainDrip.p_Next;
|
|
FirstChainDrip.p_Next = NULL;
|
|
|
|
while ( delDrip != NULL )
|
|
{
|
|
cl_drip* nextDrip = delDrip->p_Next; // save pointer to next drip in chain
|
|
delete delDrip;
|
|
delDrip = nextDrip; // restore pointer
|
|
dripcounter--;
|
|
}
|
|
|
|
// delete all FX objects
|
|
cl_rainfx* delFX = FirstChainFX.p_Next;
|
|
FirstChainFX.p_Next = NULL;
|
|
|
|
while ( delFX != NULL )
|
|
{
|
|
cl_rainfx* nextFX = delFX->p_Next;
|
|
delete delFX;
|
|
delFX = nextFX;
|
|
fxcounter--;
|
|
}
|
|
|
|
InitRain();
|
|
return;
|
|
}
|
|
|
|
/*
|
|
=================================
|
|
InitRain
|
|
initialze system
|
|
=================================
|
|
*/
|
|
void InitRain( void )
|
|
{
|
|
cl_debugrain = CVAR_REGISTER( "cl_debugrain", "0", 0 );
|
|
|
|
gHUD.Rain.dripsPerSecond = 0;
|
|
gHUD.Rain.distFromPlayer = 0;
|
|
gHUD.Rain.windX = 0;
|
|
gHUD.Rain.windY = 0;
|
|
gHUD.Rain.randX = 0;
|
|
gHUD.Rain.randY = 0;
|
|
gHUD.Rain.weatherMode = 0;
|
|
gHUD.Rain.globalHeight = 0;
|
|
|
|
FirstChainDrip.birthTime = 0;
|
|
FirstChainDrip.minHeight = 0;
|
|
FirstChainDrip.origin[0]=0;
|
|
FirstChainDrip.origin[1]=0;
|
|
FirstChainDrip.origin[2]=0;
|
|
FirstChainDrip.alpha = 0;
|
|
FirstChainDrip.xDelta = 0;
|
|
FirstChainDrip.yDelta = 0;
|
|
FirstChainDrip.landInWater = 0;
|
|
FirstChainDrip.p_Next = NULL;
|
|
FirstChainDrip.p_Prev = NULL;
|
|
|
|
FirstChainFX.alpha = 0;
|
|
FirstChainFX.birthTime = 0;
|
|
FirstChainFX.life = 0;
|
|
FirstChainFX.origin[0] = 0;
|
|
FirstChainFX.origin[1] = 0;
|
|
FirstChainFX.origin[2] = 0;
|
|
FirstChainFX.p_Next = NULL;
|
|
FirstChainFX.p_Prev = NULL;
|
|
|
|
rain_oldtime = 0;
|
|
rain_curtime = 0;
|
|
rain_nextspawntime = 0;
|
|
|
|
// engine will be free unused shaders after each change map
|
|
// so reload it again
|
|
hsprWaterRing = 0;
|
|
hsprDripTexture = 0;
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
===========================
|
|
ParseRainFile
|
|
|
|
List of settings:
|
|
drips - max raindrips\snowflakes
|
|
distance - radius of rain\snow
|
|
windx - wind shift X
|
|
windy - wind shift Y
|
|
randx - random shift X
|
|
randy - random shift Y
|
|
mode - rain = 0\snow =1
|
|
height - max height to create raindrips\snowflakes
|
|
===========================
|
|
*/
|
|
void ParseRainFile( void )
|
|
{
|
|
if ( gHUD.Rain.distFromPlayer != 0 || gHUD.Rain.dripsPerSecond != 0 || gHUD.Rain.globalHeight != 0 )
|
|
return;
|
|
|
|
char mapname[256], filepath[256];
|
|
char token[1024];
|
|
char *pfile;
|
|
char *afile;
|
|
|
|
strcpy( mapname, gEngfuncs.pfnGetLevelName() );
|
|
if ( strlen( mapname ) == 0 )
|
|
{
|
|
gEngfuncs.Con_Printf( "rain: unable to read map name\n" );
|
|
return;
|
|
}
|
|
|
|
// Xash3D always set bare mapname without path and extension
|
|
// mapname[strlen(mapname)-4] = 0;
|
|
|
|
sprintf( filepath, "scripts/weather/%s.txt", mapname );
|
|
|
|
afile = (char *)gEngfuncs.COM_LoadFile( mapname, NULL);
|
|
pfile = afile;
|
|
|
|
if ( !pfile )
|
|
{
|
|
if (cl_debugrain->value > 1 )
|
|
gEngfuncs.Con_Printf( "rain: couldn't open rain settings file %s\n", mapname );
|
|
return;
|
|
}
|
|
|
|
while ( true )
|
|
{
|
|
pfile = gEngfuncs.COM_ParseFile(pfile, token);
|
|
if (!pfile)
|
|
break;
|
|
|
|
if ( !stricmp( token, "drips" )) // dripsPerSecond
|
|
{
|
|
pfile = gEngfuncs.COM_ParseFile(pfile, token);
|
|
gHUD.Rain.dripsPerSecond = atoi( token );
|
|
}
|
|
else if ( !stricmp( token, "distance" )) // distFromPlayer
|
|
{
|
|
pfile = gEngfuncs.COM_ParseFile(pfile, token);
|
|
gHUD.Rain.distFromPlayer = atof( token );
|
|
}
|
|
else if ( !stricmp( token, "windx" )) // windX
|
|
{
|
|
pfile = gEngfuncs.COM_ParseFile(pfile, token);
|
|
gHUD.Rain.windX = atof( token );
|
|
}
|
|
else if ( !stricmp( token, "windy" )) // windY
|
|
{
|
|
pfile = gEngfuncs.COM_ParseFile(pfile, token);
|
|
gHUD.Rain.windY = atof( token );
|
|
}
|
|
else if ( !stricmp( token, "randx" )) // randX
|
|
{
|
|
pfile = gEngfuncs.COM_ParseFile(pfile, token);
|
|
gHUD.Rain.randX = atof( token );
|
|
}
|
|
else if ( !stricmp( token, "randy" )) // randY
|
|
{
|
|
pfile = gEngfuncs.COM_ParseFile(pfile, token);
|
|
gHUD.Rain.randY = atof( token );
|
|
}
|
|
else if ( !stricmp( token, "mode" )) // weatherMode
|
|
{
|
|
pfile = gEngfuncs.COM_ParseFile(pfile, token);
|
|
gHUD.Rain.weatherMode = atoi( token );
|
|
}
|
|
else if ( !stricmp( token, "height" )) // globalHeight
|
|
{
|
|
pfile = gEngfuncs.COM_ParseFile(pfile, token);
|
|
gHUD.Rain.globalHeight = atof( token );
|
|
}
|
|
else gEngfuncs.Con_Printf( "rain: unknown token %s in file %s\n", token, mapname );
|
|
}
|
|
|
|
gEngfuncs.COM_FreeFile( afile );
|
|
}
|
|
|
|
//-----------------------------------------------------
|
|
|
|
void SetPoint( float x, float y, float z, float (*matrix)[4] )
|
|
{
|
|
Vector point = Vector( x, y, z ), result;
|
|
|
|
result.x = DotProduct( point, matrix[0] ) + matrix[0][3];
|
|
result.y = DotProduct( point, matrix[1] ) + matrix[1][3];
|
|
result.z = DotProduct( point, matrix[2] ) + matrix[2][3];
|
|
|
|
gEngfuncs.pTriAPI->Vertex3f( result[0], result[1], result[2] );
|
|
}
|
|
|
|
/*
|
|
=================================
|
|
DrawRain
|
|
|
|
draw raindrips and snowflakes
|
|
=================================
|
|
*/
|
|
void DrawRain( void )
|
|
{
|
|
if ( FirstChainDrip.p_Next == NULL )
|
|
return; // no drips to draw
|
|
|
|
if( hsprDripTexture == 0 )
|
|
{
|
|
if ( gHUD.Rain.weatherMode == 0 )
|
|
{
|
|
// load rain sprite
|
|
int modelIndex = gEngfuncs.pEventAPI->EV_FindModelIndex( "sprites/hi_rain.spr" );
|
|
hsprDripTexture = gEngfuncs.pTriAPI->GetSpriteTexture( modelIndex, 0 );
|
|
}
|
|
else
|
|
{
|
|
// load snow sprite
|
|
int modelIndex = gEngfuncs.pEventAPI->EV_FindModelIndex( "sprites/snowflake.spr" );
|
|
hsprDripTexture = gEngfuncs.pTriAPI->GetSpriteTexture( modelIndex, 0 );
|
|
}
|
|
}
|
|
if( hsprDripTexture <= 0 ) return;
|
|
|
|
float visibleHeight = gHUD.Rain.globalHeight - SNOWFADEDIST;
|
|
|
|
// usual triapi stuff
|
|
gEngfuncs.pTriAPI->Enable( TRI_SHADER );
|
|
gEngfuncs.pTriAPI->Bind( hsprDripTexture, 0 );
|
|
gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd );
|
|
gEngfuncs.pTriAPI->CullFace( TRI_NONE );
|
|
|
|
// go through drips list
|
|
cl_drip *Drip = FirstChainDrip.p_Next;
|
|
cl_entity_t *player = GetLocalPlayer();
|
|
if( !player ) return; // not in game yet
|
|
|
|
if ( gHUD.Rain.weatherMode == 0 ) // draw rain
|
|
{
|
|
while ( Drip != NULL )
|
|
{
|
|
cl_drip* nextdDrip = Drip->p_Next;
|
|
|
|
Vector2D toPlayer;
|
|
toPlayer.x = player->origin[0] - Drip->origin[0];
|
|
toPlayer.y = player->origin[1] - Drip->origin[1];
|
|
toPlayer = toPlayer.Normalize();
|
|
|
|
toPlayer.x *= DRIP_SPRITE_HALFWIDTH;
|
|
toPlayer.y *= DRIP_SPRITE_HALFWIDTH;
|
|
|
|
float shiftX = (Drip->xDelta / DRIPSPEED) * DRIP_SPRITE_HALFHEIGHT;
|
|
float shiftY = (Drip->yDelta / DRIPSPEED) * DRIP_SPRITE_HALFHEIGHT;
|
|
|
|
gEngfuncs.pTriAPI->Color4f( 1.0, 1.0, 1.0, Drip->alpha );
|
|
gEngfuncs.pTriAPI->Begin( TRI_TRIANGLES );
|
|
|
|
gEngfuncs.pTriAPI->TexCoord2f( 0, 0 );
|
|
gEngfuncs.pTriAPI->Vertex3f( Drip->origin[0]-toPlayer.y - shiftX, Drip->origin[1]+toPlayer.x - shiftY,Drip->origin[2] + DRIP_SPRITE_HALFHEIGHT );
|
|
|
|
gEngfuncs.pTriAPI->TexCoord2f( 0.5, 1 );
|
|
gEngfuncs.pTriAPI->Vertex3f( Drip->origin[0] + shiftX, Drip->origin[1] + shiftY, Drip->origin[2]-DRIP_SPRITE_HALFHEIGHT );
|
|
|
|
gEngfuncs.pTriAPI->TexCoord2f( 1, 0 );
|
|
gEngfuncs.pTriAPI->Vertex3f( Drip->origin[0]+toPlayer.y - shiftX, Drip->origin[1]-toPlayer.x - shiftY, Drip->origin[2]+DRIP_SPRITE_HALFHEIGHT);
|
|
|
|
gEngfuncs.pTriAPI->End();
|
|
Drip = nextdDrip;
|
|
}
|
|
}
|
|
|
|
else // draw snow
|
|
{
|
|
Vector normal;
|
|
|
|
GetViewAngles( (float *)normal );
|
|
|
|
float matrix[3][4];
|
|
AngleMatrix( normal, matrix ); // calc view matrix
|
|
|
|
while ( Drip != NULL )
|
|
{
|
|
cl_drip* nextdDrip = Drip->p_Next;
|
|
|
|
matrix[0][3] = Drip->origin[0]; // write origin to matrix
|
|
matrix[1][3] = Drip->origin[1];
|
|
matrix[2][3] = Drip->origin[2];
|
|
|
|
// apply start fading effect
|
|
float alpha = (Drip->origin[2] <= visibleHeight) ? Drip->alpha : ((gHUD.Rain.globalHeight - Drip->origin[2]) / (float)SNOWFADEDIST) * Drip->alpha;
|
|
|
|
gEngfuncs.pTriAPI->Color4f( 1.0, 1.0, 1.0, alpha );
|
|
gEngfuncs.pTriAPI->Begin( TRI_QUADS );
|
|
|
|
gEngfuncs.pTriAPI->TexCoord2f( 0, 0 );
|
|
SetPoint( 0, SNOW_SPRITE_HALFSIZE, SNOW_SPRITE_HALFSIZE, matrix );
|
|
|
|
gEngfuncs.pTriAPI->TexCoord2f( 0, 1 );
|
|
SetPoint( 0, SNOW_SPRITE_HALFSIZE, -SNOW_SPRITE_HALFSIZE, matrix );
|
|
|
|
gEngfuncs.pTriAPI->TexCoord2f( 1, 1 );
|
|
SetPoint( 0, -SNOW_SPRITE_HALFSIZE, -SNOW_SPRITE_HALFSIZE, matrix );
|
|
|
|
gEngfuncs.pTriAPI->TexCoord2f( 1, 0 );
|
|
SetPoint( 0, -SNOW_SPRITE_HALFSIZE, SNOW_SPRITE_HALFSIZE, matrix );
|
|
|
|
gEngfuncs.pTriAPI->End();
|
|
Drip = nextdDrip;
|
|
}
|
|
}
|
|
gEngfuncs.pTriAPI->Disable( TRI_SHADER );
|
|
}
|
|
|
|
/*
|
|
=================================
|
|
DrawFXObjects
|
|
=================================
|
|
*/
|
|
void DrawFXObjects( void )
|
|
{
|
|
if ( FirstChainFX.p_Next == NULL )
|
|
return; // no objects to draw
|
|
|
|
float curtime = GetClientTime();
|
|
|
|
// usual triapi stuff
|
|
if( hsprWaterRing == 0 ) // in case what we don't want search it if not found
|
|
{
|
|
// load water ring sprite
|
|
int modelIndex = gEngfuncs.pEventAPI->EV_FindModelIndex( "sprites/waterring.spr" );
|
|
hsprWaterRing = gEngfuncs.pTriAPI->GetSpriteTexture( modelIndex, 0 );
|
|
}
|
|
|
|
if( hsprWaterRing <= 0 ) return; // don't waste time
|
|
|
|
gEngfuncs.pTriAPI->Enable( TRI_SHADER );
|
|
gEngfuncs.pTriAPI->Bind( hsprWaterRing, 0 );
|
|
gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd );
|
|
gEngfuncs.pTriAPI->CullFace( TRI_NONE );
|
|
|
|
// go through objects list
|
|
cl_rainfx* curFX = FirstChainFX.p_Next;
|
|
while ( curFX != NULL )
|
|
{
|
|
cl_rainfx* nextFX = curFX->p_Next;
|
|
|
|
// fadeout
|
|
float alpha = ((curFX->birthTime + curFX->life - curtime) / curFX->life) * curFX->alpha;
|
|
float size = (curtime - curFX->birthTime) * MAXRINGHALFSIZE;
|
|
float color[3];
|
|
|
|
// UNDONE: calc lighting right
|
|
gEngfuncs.pEfxAPI->R_LightForPoint( (const float *)curFX->origin, color );
|
|
// gEngfuncs.Con_Printf( "color %g %g %g\n", color[0], color[1], color[2] );
|
|
|
|
gEngfuncs.pTriAPI->Color4f( 0.4 + color[0], 0.4 + color[1], 0.4 + color[2], alpha );
|
|
gEngfuncs.pTriAPI->Begin( TRI_QUADS );
|
|
|
|
gEngfuncs.pTriAPI->TexCoord2f( 0, 0 );
|
|
gEngfuncs.pTriAPI->Vertex3f( curFX->origin[0] - size, curFX->origin[1] - size, curFX->origin[2] );
|
|
|
|
gEngfuncs.pTriAPI->TexCoord2f( 0, 1 );
|
|
gEngfuncs.pTriAPI->Vertex3f( curFX->origin[0] - size, curFX->origin[1] + size, curFX->origin[2] );
|
|
|
|
gEngfuncs.pTriAPI->TexCoord2f( 1, 1 );
|
|
gEngfuncs.pTriAPI->Vertex3f( curFX->origin[0] + size, curFX->origin[1] + size, curFX->origin[2] );
|
|
|
|
gEngfuncs.pTriAPI->TexCoord2f( 1, 0 );
|
|
gEngfuncs.pTriAPI->Vertex3f( curFX->origin[0] + size, curFX->origin[1] - size, curFX->origin[2] );
|
|
|
|
gEngfuncs.pTriAPI->End();
|
|
curFX = nextFX;
|
|
}
|
|
gEngfuncs.pTriAPI->Disable( TRI_SHADER );
|
|
}
|
|
|
|
/*
|
|
=================================
|
|
DrawAll
|
|
=================================
|
|
*/
|
|
|
|
void R_DrawWeather( void )
|
|
{
|
|
ProcessFXObjects();
|
|
ProcessRain();
|
|
DrawRain();
|
|
DrawFXObjects();
|
|
} |