Files
xash3d-fwgs/ref/gl/gl_rmain.c
Alibek Omarov e87b14468d ref: when entity model is NULL, the entity is player and we're drawing in menu, skip this entity silently
The menu relies on the fact that model is set from player's userinfo, and if it has bogus model, it will
trip on the nullptr model assert
2026-05-26 02:57:47 +05:00

1274 lines
29 KiB
C

/*
gl_rmain.c - renderer main loop
Copyright (C) 2010 Uncle Mike
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#include "gl_local.h"
#include "xash3d_mathlib.h"
#include "library.h"
#include "beamdef.h"
#include "entity_types.h"
#define IsLiquidContents( cnt ) ( cnt == CONTENTS_WATER || cnt == CONTENTS_SLIME || cnt == CONTENTS_LAVA )
float gldepthmin, gldepthmax;
ref_instance_t RI;
static int R_RankForRenderMode( int rendermode )
{
switch( rendermode )
{
case kRenderTransTexture:
return 1; // draw second
case kRenderTransAdd:
return 2; // draw third
case kRenderGlow:
return 3; // must be last!
}
return 0;
}
void R_AllowFog( qboolean allowed )
{
if( allowed )
{
if( glState.isFogEnabled && gl_fog.value )
pglEnable( GL_FOG );
}
else
{
if( glState.isFogEnabled )
pglDisable( GL_FOG );
}
}
/*
===============
R_OpaqueEntity
Opaque entity can be brush or studio model but sprite
===============
*/
qboolean R_OpaqueEntity( cl_entity_t *ent )
{
if( R_GetEntityRenderMode( ent ) == kRenderNormal )
{
switch( ent->curstate.renderfx )
{
case kRenderFxNone:
case kRenderFxDeadPlayer:
case kRenderFxLightMultiplier:
case kRenderFxExplode:
return true;
}
}
return false;
}
/*
===============
R_TransEntityCompare
Sorting translucent entities by rendermode then by distance
===============
*/
static int R_TransEntityCompare( const void *a, const void *b )
{
cl_entity_t *ent1 = *(cl_entity_t **)a;
cl_entity_t *ent2 = *(cl_entity_t **)b;
int rendermode1 = R_GetEntityRenderMode( ent1 );
int rendermode2 = R_GetEntityRenderMode( ent2 );
vec3_t vecLen, org;
float dist1, dist2;
// sort by distance
if( ent1->model->type != mod_brush || rendermode1 != kRenderTransAlpha )
{
VectorAverage( ent1->model->mins, ent1->model->maxs, org );
VectorAdd( ent1->origin, org, org );
VectorSubtract( RI.rvp.vieworigin, org, vecLen );
dist1 = DotProduct( vecLen, vecLen );
}
else dist1 = 1000000000;
if( ent2->model->type != mod_brush || rendermode2 != kRenderTransAlpha )
{
VectorAverage( ent2->model->mins, ent2->model->maxs, org );
VectorAdd( ent2->origin, org, org );
VectorSubtract( RI.rvp.vieworigin, org, vecLen );
dist2 = DotProduct( vecLen, vecLen );
}
else dist2 = 1000000000;
if( dist1 > dist2 )
return -1;
if( dist1 < dist2 )
return 1;
// then sort by rendermode
if( R_RankForRenderMode( rendermode1 ) > R_RankForRenderMode( rendermode2 ))
return 1;
if( R_RankForRenderMode( rendermode1 ) < R_RankForRenderMode( rendermode2 ))
return -1;
return 0;
}
/*
===============
R_WorldToScreen
Convert a given point from world into screen space
Returns true if we behind to screen
===============
*/
int R_WorldToScreen( const vec3_t point, vec3_t screen )
{
if( !point || !screen )
return true;
matrix4x4 worldToScreen;
Matrix4x4_Copy( worldToScreen, RI.worldviewProjectionMatrix );
screen[0] = worldToScreen[0][0] * point[0] + worldToScreen[0][1] * point[1] + worldToScreen[0][2] * point[2] + worldToScreen[0][3];
screen[1] = worldToScreen[1][0] * point[0] + worldToScreen[1][1] * point[1] + worldToScreen[1][2] * point[2] + worldToScreen[1][3];
float w = worldToScreen[3][0] * point[0] + worldToScreen[3][1] * point[1] + worldToScreen[3][2] * point[2] + worldToScreen[3][3];
screen[2] = 0.0f; // just so we have something valid here
qboolean behind;
if( w < 0.001f )
{
behind = true;
}
else
{
float invw = 1.0f / w;
screen[0] *= invw;
screen[1] *= invw;
behind = false;
}
return behind;
}
/*
===============
R_ScreenToWorld
Convert a given point from screen into world space
===============
*/
void R_ScreenToWorld( const vec3_t screen, vec3_t point )
{
if( !point || !screen )
return;
matrix4x4 screenToWorld;
Matrix4x4_Invert_Full( screenToWorld, RI.worldviewProjectionMatrix );
point[0] = screen[0] * screenToWorld[0][0] + screen[1] * screenToWorld[0][1] + screen[2] * screenToWorld[0][2] + screenToWorld[0][3];
point[1] = screen[0] * screenToWorld[1][0] + screen[1] * screenToWorld[1][1] + screen[2] * screenToWorld[1][2] + screenToWorld[1][3];
point[2] = screen[0] * screenToWorld[2][0] + screen[1] * screenToWorld[2][1] + screen[2] * screenToWorld[2][2] + screenToWorld[2][3];
float w = screen[0] * screenToWorld[3][0] + screen[1] * screenToWorld[3][1] + screen[2] * screenToWorld[3][2] + screenToWorld[3][3];
if( w != 0.0f ) VectorScale( point, ( 1.0f / w ), point );
}
/*
===============
R_PushScene
===============
*/
void R_PushScene( void )
{
if( ++tr.draw_stack_pos >= MAX_DRAW_STACK )
gEngfuncs.Host_Error( "draw stack overflow\n" );
tr.draw_list = &tr.draw_stack[tr.draw_stack_pos];
}
/*
===============
R_PopScene
===============
*/
void R_PopScene( void )
{
if( --tr.draw_stack_pos < 0 )
gEngfuncs.Host_Error( "draw stack underflow\n" );
tr.draw_list = &tr.draw_stack[tr.draw_stack_pos];
}
/*
===============
R_ClearScene
===============
*/
void R_ClearScene( void )
{
tr.draw_list->num_solid_entities = 0;
tr.draw_list->num_trans_entities = 0;
tr.draw_list->num_beam_entities = 0;
// clear the scene befor start new frame
if( gEngfuncs.drawFuncs->R_ClearScene != NULL )
gEngfuncs.drawFuncs->R_ClearScene();
}
/*
===============
R_AddEntity
===============
*/
qboolean R_AddEntity( struct cl_entity_s *clent, int type )
{
if( !r_drawentities->value )
return false; // not allow to drawing
if( FBitSet( clent->curstate.effects, EF_NODRAW ))
return false; // done
if( !R_ModelOpaque( clent->curstate.rendermode ) && CL_FxBlend( clent ) <= 0 )
return true; // invisible
switch( type )
{
case ET_FRAGMENTED:
r_stats.c_client_ents++;
break;
case ET_TEMPENTITY:
r_stats.c_active_tents_count++;
break;
}
if( type == ET_BEAM )
{
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 false;
}
tr.draw_list->beam_entities[tr.draw_list->num_beam_entities] = clent;
tr.draw_list->num_beam_entities++;
return true;
}
else if( R_OpaqueEntity( clent ))
{
// opaque
if( tr.draw_list->num_solid_entities >= MAX_VISIBLE_PACKET )
return false;
tr.draw_list->solid_entities[tr.draw_list->num_solid_entities] = clent;
tr.draw_list->num_solid_entities++;
}
else
{
// translucent
if( tr.draw_list->num_trans_entities >= MAX_VISIBLE_PACKET )
return false;
tr.draw_list->trans_entities[tr.draw_list->num_trans_entities] = clent;
tr.draw_list->num_trans_entities++;
}
return true;
}
/*
=============
R_Clear
=============
*/
static void R_Clear( int bitMask )
{
if( ENGINE_GET_PARM( PARM_DEV_OVERVIEW ))
pglClearColor( 0.0f, 1.0f, 0.0f, 1.0f ); // green background (Valve rules)
else pglClearColor( 0.5f, 0.5f, 0.5f, 1.0f );
int bits = GL_DEPTH_BUFFER_BIT;
if( glState.stencilEnabled )
bits |= GL_STENCIL_BUFFER_BIT;
bits &= bitMask;
pglClear( bits );
// change ordering for overview
if( FBitSet( RI.rvp.flags, RF_DRAW_OVERVIEW ))
{
gldepthmin = 1.0f;
gldepthmax = 0.0f;
}
else
{
gldepthmin = 0.0f;
gldepthmax = 1.0f;
}
pglDepthFunc( GL_LEQUAL );
pglDepthRange( gldepthmin, gldepthmax );
}
//=============================================================================
/*
===============
R_GetFarClip
===============
*/
static float R_GetFarClip( void )
{
if( WORLDMODEL && FBitSet( RI.rvp.flags, RF_DRAW_WORLD ))
return gp_movevars->zmax * 1.73f;
return 2048.0f;
}
/*
===============
R_SetupFrustum
===============
*/
void R_SetupFrustum( void )
{
// build the transformation matrix for the given view angles
AngleVectors( RI.rvp.viewangles, RI.vforward, RI.vright, RI.vup );
if( !r_lockfrustum.value )
{
VectorCopy( RI.rvp.vieworigin, RI.cullorigin );
VectorCopy( RI.vforward, RI.cull_vforward );
VectorCopy( RI.vright, RI.cull_vright );
VectorCopy( RI.vup, RI.cull_vup );
}
if( FBitSet( RI.rvp.flags, RF_DRAW_OVERVIEW ))
{
const ref_overview_t *ov = gEngfuncs.GetOverviewParms();
GL_FrustumInitOrtho( &RI.frustum, ov->xLeft, ov->xRight, ov->yTop, ov->yBottom, ov->zNear, ov->zFar );
}
else
{
GL_FrustumInitProj( &RI.frustum, 0.0f, R_GetFarClip(), RI.rvp.fov_x, RI.rvp.fov_y ); // NOTE: we ignore nearplane here (mirrors only)
}
}
/*
=============
R_SetupProjectionMatrix
=============
*/
static void R_SetupProjectionMatrix( matrix4x4 m )
{
if( FBitSet( RI.rvp.flags, RF_DRAW_OVERVIEW ))
{
const ref_overview_t *ov = gEngfuncs.GetOverviewParms();
Matrix4x4_CreateOrtho( m, ov->xLeft, ov->xRight, ov->yTop, ov->yBottom, ov->zNear, ov->zFar );
return;
}
RI.farClip = R_GetFarClip();
GLfloat zNear = 4.0f;
GLfloat zFar = Q_max( 256.0f, RI.farClip );
GLfloat yMax = zNear * tan( RI.rvp.fov_y * M_PI_F / 360.0f );
GLfloat yMin = -yMax;
GLfloat xMax = zNear * tan( RI.rvp.fov_x * M_PI_F / 360.0f );
GLfloat xMin = -xMax;
if( tr.rotation & 1 )
{
Matrix4x4_CreateProjection( m, yMax, yMin, xMax, xMin, zNear, zFar );
}
else
{
Matrix4x4_CreateProjection( m, xMax, xMin, yMax, yMin, zNear, zFar );
}
}
/*
=============
R_SetupModelviewMatrix
=============
*/
static void R_SetupModelviewMatrix( matrix4x4 m )
{
Matrix4x4_CreateModelview( m );
if( tr.rotation & 1 )
{
Matrix4x4_ConcatRotate( m, anglemod( -RI.rvp.viewangles[2] + 90 ), 1, 0, 0 );
Matrix4x4_ConcatRotate( m, -RI.rvp.viewangles[0], 0, 1, 0 );
Matrix4x4_ConcatRotate( m, -RI.rvp.viewangles[1], 0, 0, 1 );
}
else
{
Matrix4x4_ConcatRotate( m, -RI.rvp.viewangles[2], 1, 0, 0 );
Matrix4x4_ConcatRotate( m, -RI.rvp.viewangles[0], 0, 1, 0 );
Matrix4x4_ConcatRotate( m, -RI.rvp.viewangles[1], 0, 0, 1 );
}
Matrix4x4_ConcatTranslate( m, -RI.rvp.vieworigin[0], -RI.rvp.vieworigin[1], -RI.rvp.vieworigin[2] );
}
/*
=============
R_LoadIdentity
=============
*/
void R_LoadIdentity( void )
{
if( tr.modelviewIdentity ) return;
Matrix4x4_LoadIdentity( RI.objectMatrix );
Matrix4x4_Copy( RI.modelviewMatrix, RI.worldviewMatrix );
pglMatrixMode( GL_MODELVIEW );
GL_LoadMatrix( RI.modelviewMatrix );
tr.modelviewIdentity = true;
}
/*
=============
R_RotateForEntity
=============
*/
void R_RotateForEntity( cl_entity_t *e )
{
float scale = 1.0f;
if( e == CL_GetEntityByIndex( 0 ))
{
R_LoadIdentity();
return;
}
if( e->model->type != mod_brush && e->curstate.scale > 0.0f )
scale = e->curstate.scale;
Matrix4x4_CreateFromEntity( RI.objectMatrix, e->angles, e->origin, scale );
Matrix4x4_ConcatTransforms( RI.modelviewMatrix, RI.worldviewMatrix, RI.objectMatrix );
pglMatrixMode( GL_MODELVIEW );
GL_LoadMatrix( RI.modelviewMatrix );
tr.modelviewIdentity = false;
}
/*
=============
R_TranslateForEntity
=============
*/
void R_TranslateForEntity( cl_entity_t *e )
{
float scale = 1.0f;
if( e == CL_GetEntityByIndex( 0 ))
{
R_LoadIdentity();
return;
}
if( e->model->type != mod_brush && e->curstate.scale > 0.0f )
scale = e->curstate.scale;
Matrix4x4_CreateFromEntity( RI.objectMatrix, vec3_origin, e->origin, scale );
Matrix4x4_ConcatTransforms( RI.modelviewMatrix, RI.worldviewMatrix, RI.objectMatrix );
pglMatrixMode( GL_MODELVIEW );
GL_LoadMatrix( RI.modelviewMatrix );
tr.modelviewIdentity = false;
}
/*
===============
R_FindViewLeaf
===============
*/
void R_FindViewLeaf( void )
{
RI.oldviewleaf = RI.viewleaf;
RI.viewleaf = gEngfuncs.Mod_PointInLeaf( RI.rvp.vieworigin, WORLDMODEL->nodes, WORLDMODEL );
}
/*
===============
R_SetupFrame
===============
*/
static void R_SetupFrame( void )
{
// setup viewplane dist
RI.viewplanedist = DotProduct( RI.rvp.vieworigin, RI.vforward );
// NOTE: this request is the fps-killer on some NVidia drivers
glState.isFogEnabled = pglIsEnabled( GL_FOG );
if( !gl_nosort.value )
{
// sort translucents entities by rendermode and distance
qsort( tr.draw_list->trans_entities, tr.draw_list->num_trans_entities, sizeof( cl_entity_t* ), R_TransEntityCompare );
}
// current viewleaf
if( FBitSet( RI.rvp.flags, RF_DRAW_WORLD ))
R_FindViewLeaf();
}
/*
=============
R_SetupGL
=============
*/
void R_SetupGL( qboolean set_gl_state )
{
R_SetupModelviewMatrix( RI.worldviewMatrix );
R_SetupProjectionMatrix( RI.projectionMatrix );
Matrix4x4_Concat( RI.worldviewProjectionMatrix, RI.projectionMatrix, RI.worldviewMatrix );
if( !set_gl_state ) return;
if( !FBitSet( RI.rvp.flags, RF_DRAW_CUBEMAP ))
{
// set up viewport (main, playersetup)
int x = floor( RI.rvp.viewport[0] * gpGlobals->width / gpGlobals->width );
int x2 = ceil(( RI.rvp.viewport[0] + RI.rvp.viewport[2] ) * gpGlobals->width / gpGlobals->width );
int y = floor( gpGlobals->height - RI.rvp.viewport[1] * gpGlobals->height / gpGlobals->height );
int y2 = ceil( gpGlobals->height - ( RI.rvp.viewport[1] + RI.rvp.viewport[3] ) * gpGlobals->height / gpGlobals->height );
if( tr.rotation & 1 )
pglViewport( y2, x, y - y2, x2 - x );
else pglViewport( x, y2, x2 - x, y - y2 );
}
else
{
// envpass, mirrorpass
pglViewport( RI.rvp.viewport[0], RI.rvp.viewport[1], RI.rvp.viewport[2], RI.rvp.viewport[3] );
}
pglMatrixMode( GL_PROJECTION );
GL_LoadMatrix( RI.projectionMatrix );
pglMatrixMode( GL_MODELVIEW );
GL_LoadMatrix( RI.worldviewMatrix );
GL_Cull( GL_FRONT );
pglDisable( GL_BLEND );
pglDisable( GL_ALPHA_TEST );
pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
}
/*
=============
R_RecursiveFindWaterTexture
using to find source waterleaf with
watertexture to grab fog values from it
=============
*/
static gl_texture_t *R_RecursiveFindWaterTexture( const mnode_t *node, const mnode_t *ignore, qboolean down )
{
gl_texture_t *tex = NULL;
// assure the initial node is not null
// we could check it here, but we would rather check it
// outside the call to get rid of one additional recursion level
Assert( node != NULL );
// ignore solid nodes
if( node->contents == CONTENTS_SOLID )
return NULL;
if( node->contents < 0 )
{
// ignore non-liquid leaves
if( node->contents != CONTENTS_WATER && node->contents != CONTENTS_LAVA && node->contents != CONTENTS_SLIME )
return NULL;
// find texture
mleaf_t *pleaf = (mleaf_t *)node;
msurface_t **mark = pleaf->firstmarksurface;
int c = pleaf->nummarksurfaces;
for( int i = 0; i < c; i++, mark++ )
{
if( (*mark)->flags & SURF_DRAWTURB && (*mark)->texinfo && (*mark)->texinfo->texture )
return R_GetTexture( (*mark)->texinfo->texture->gl_texturenum );
}
// texture not found
return NULL;
}
// this is a regular node
// traverse children
mnode_t *child = node_child( node, 0, WORLDMODEL );
if( child && ( child != ignore ))
{
tex = R_RecursiveFindWaterTexture( child, node, true );
if( tex ) return tex;
}
child = node_child( node, 1, WORLDMODEL );
if( child && ( child != ignore ))
{
tex = R_RecursiveFindWaterTexture( child, node, true );
if( tex ) return tex;
}
// for down recursion, return immediately
if( down ) return NULL;
// texture not found, step up if any
if( node->parent )
return R_RecursiveFindWaterTexture( node->parent, node, false );
// top-level node, bail out
return NULL;
}
/*
=============
R_CheckFog
check for underwater fog
Using backward recursion to find waterline leaf
from underwater leaf (idea: XaeroX)
=============
*/
static void R_CheckFog( void )
{
// quake global fog
if( FBitSet( gp_host->features, ENGINE_QUAKE_COMPATIBLE ))
{
if( !gp_movevars->fog_settings )
{
if( pglIsEnabled( GL_FOG ))
pglDisable( GL_FOG );
RI.fogEnabled = false;
return;
}
// quake-style global fog
RI.fogColor[0] = ((gp_movevars->fog_settings & 0xFF000000) >> 24) / 255.0f;
RI.fogColor[1] = ((gp_movevars->fog_settings & 0xFF0000) >> 16) / 255.0f;
RI.fogColor[2] = ((gp_movevars->fog_settings & 0xFF00) >> 8) / 255.0f;
RI.fogDensity = ((gp_movevars->fog_settings & 0xFF) / 255.0f) * 0.01f;
RI.fogStart = RI.fogEnd = 0.0f;
RI.fogColor[3] = 1.0f;
RI.fogCustom = false;
RI.fogEnabled = true;
RI.fogSkybox = true;
return;
}
RI.fogEnabled = false;
if( FBitSet( RI.rvp.flags, RF_ONLY_CLIENTDRAW ) || ENGINE_GET_PARM( PARM_WATER_LEVEL ) < 3 || !FBitSet( RI.rvp.flags, RF_DRAW_WORLD ) || !RI.viewleaf )
{
if( RI.cached_waterlevel == 3 )
{
// in some cases waterlevel jumps from 3 to 1. Catch it
RI.cached_waterlevel = ENGINE_GET_PARM( PARM_WATER_LEVEL );
RI.cached_contents = CONTENTS_EMPTY;
if( !RI.fogCustom )
{
glState.isFogEnabled = false;
pglDisable( GL_FOG );
}
}
return;
}
cl_entity_t *ent = gEngfuncs.CL_GetWaterEntity( RI.rvp.vieworigin );
int cnt;
if( ent && ent->model && ent->model->type == mod_brush && ent->curstate.skin < 0 )
cnt = ent->curstate.skin;
else cnt = RI.viewleaf->contents;
RI.cached_waterlevel = ENGINE_GET_PARM( PARM_WATER_LEVEL );
if( !IsLiquidContents( RI.cached_contents ) && IsLiquidContents( cnt ))
{
gl_texture_t *tex = NULL;
// check for water texture
if( ent && ent->model && ent->model->type == mod_brush )
{
int count = ent->model->nummodelsurfaces;
msurface_t *surf = &ent->model->surfaces[ent->model->firstmodelsurface];
for( int i = 0; i < count; i++, surf++ )
{
if( surf->flags & SURF_DRAWTURB && surf->texinfo && surf->texinfo->texture )
{
tex = R_GetTexture( surf->texinfo->texture->gl_texturenum );
RI.cached_contents = ent->curstate.skin;
break;
}
}
}
else
{
tex = R_RecursiveFindWaterTexture( RI.viewleaf->parent, NULL, false );
if( tex ) RI.cached_contents = RI.viewleaf->contents;
}
if( !tex ) return; // no valid fogs
// copy fog params
RI.fogColor[0] = tex->fogParams[0] / 255.0f;
RI.fogColor[1] = tex->fogParams[1] / 255.0f;
RI.fogColor[2] = tex->fogParams[2] / 255.0f;
RI.fogDensity = tex->fogParams[3] * 0.000025f;
RI.fogStart = RI.fogEnd = 0.0f;
RI.fogColor[3] = 1.0f;
RI.fogCustom = false;
RI.fogEnabled = true;
RI.fogSkybox = true;
}
else
{
RI.fogCustom = false;
RI.fogEnabled = true;
RI.fogSkybox = true;
}
}
/*
=============
R_CheckGLFog
special condition for Spirit 1.9
that used direct calls of glFog-functions
=============
*/
static void R_CheckGLFog( void )
{
#ifdef HACKS_RELATED_HLMODS
if(( !RI.fogEnabled && !RI.fogCustom ) && pglIsEnabled( GL_FOG ) && VectorIsNull( RI.fogColor ))
{
// fill the fog color from GL-state machine
pglGetFloatv( GL_FOG_COLOR, RI.fogColor );
RI.fogSkybox = true;
}
#endif
}
/*
=============
R_DrawFog
=============
*/
void R_DrawFog( void )
{
if( !RI.fogEnabled || !gl_fog.value )
return;
pglEnable( GL_FOG );
pglFogi( GL_FOG_MODE, FBitSet( gp_host->features, ENGINE_QUAKE_COMPATIBLE ) ? GL_EXP2 : GL_EXP );
pglFogf( GL_FOG_DENSITY, RI.fogDensity );
pglFogfv( GL_FOG_COLOR, RI.fogColor );
pglHint( GL_FOG_HINT, GL_NICEST );
}
/*
=============
R_DrawEntitiesOnList
=============
*/
static void R_DrawEntitiesOnList( void )
{
tr.blend = 1.0f;
GL_CheckForErrors();
// first draw solid entities
for( int i = 0; i < tr.draw_list->num_solid_entities && !FBitSet( RI.rvp.flags, RF_ONLY_CLIENTDRAW ); i++ )
{
RI.currententity = tr.draw_list->solid_entities[i];
RI.currentmodel = RI.currententity->model;
if( !RI.currentmodel && RI.currententity->player && !FBitSet( RI.rvp.flags, RF_DRAW_WORLD ))
continue;
Assert( RI.currententity != NULL );
Assert( RI.currentmodel != NULL );
switch( RI.currentmodel->type )
{
case mod_brush:
R_DrawBrushModel( RI.currententity );
break;
case mod_alias:
R_DrawAliasModel( RI.currententity );
break;
case mod_studio:
R_DrawStudioModel( RI.currententity );
break;
default:
break;
}
}
GL_CheckForErrors();
// quake-specific feature
R_DrawAlphaTextureChains();
GL_CheckForErrors();
// draw sprites seperately, because of alpha blending
for( int i = 0; i < tr.draw_list->num_solid_entities && !FBitSet( RI.rvp.flags, RF_ONLY_CLIENTDRAW ); i++ )
{
RI.currententity = tr.draw_list->solid_entities[i];
RI.currentmodel = RI.currententity->model;
if( !RI.currentmodel && RI.currententity->player && !FBitSet( RI.rvp.flags, RF_DRAW_WORLD ))
continue;
Assert( RI.currententity != NULL );
Assert( RI.currentmodel != NULL );
switch( RI.currentmodel->type )
{
case mod_sprite:
R_DrawSpriteModel( RI.currententity );
break;
}
}
GL_CheckForErrors();
if( !FBitSet( RI.rvp.flags, RF_ONLY_CLIENTDRAW ))
{
gEngfuncs.CL_DrawEFX( tr.frametime, false );
}
GL_CheckForErrors();
if( FBitSet( RI.rvp.flags, RF_DRAW_WORLD ))
gEngfuncs.pfnDrawNormalTriangles();
GL_CheckForErrors();
// then draw translucent entities
for( int i = 0; i < tr.draw_list->num_trans_entities && !FBitSet( RI.rvp.flags, RF_ONLY_CLIENTDRAW ); i++ )
{
RI.currententity = tr.draw_list->trans_entities[i];
RI.currentmodel = RI.currententity->model;
// handle studiomodels with custom rendermodes on texture
if( RI.currententity->curstate.rendermode != kRenderNormal )
tr.blend = CL_FxBlend( RI.currententity ) / 255.0f;
else tr.blend = 1.0f; // draw as solid but sorted by distance
if( tr.blend <= 0.0f ) continue;
if( !RI.currentmodel && RI.currententity->player && !FBitSet( RI.rvp.flags, RF_DRAW_WORLD ))
continue;
Assert( RI.currententity != NULL );
Assert( RI.currentmodel != NULL );
switch( RI.currentmodel->type )
{
case mod_brush:
R_DrawBrushModel( RI.currententity );
break;
case mod_alias:
R_DrawAliasModel( RI.currententity );
break;
case mod_studio:
R_DrawStudioModel( RI.currententity );
break;
case mod_sprite:
R_DrawSpriteModel( RI.currententity );
break;
default:
break;
}
}
GL_CheckForErrors();
if( FBitSet( RI.rvp.flags, RF_DRAW_WORLD ))
{
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
gEngfuncs.pfnDrawTransparentTriangles ();
}
GL_CheckForErrors();
if( !FBitSet( RI.rvp.flags, RF_ONLY_CLIENTDRAW ))
{
R_AllowFog( false );
gEngfuncs.CL_DrawEFX( tr.frametime, true );
R_AllowFog( true );
}
GL_CheckForErrors();
pglDisable( GL_BLEND ); // Trinity Render issues
if( !FBitSet( RI.rvp.flags, RF_ONLY_CLIENTDRAW ))
R_DrawViewModel();
gEngfuncs.CL_ExtraUpdate();
GL_CheckForErrors();
}
/*
================
R_RenderScene
R_SetupRefParams must be called right before
================
*/
void R_RenderScene( void )
{
if( !WORLDMODEL && FBitSet( RI.rvp.flags, RF_DRAW_WORLD ))
gEngfuncs.Host_Error( "%s: NULL worldmodel\n", __func__ );
// frametime is valid only for normal pass
if( !FBitSet( RI.rvp.flags, RF_DRAW_CUBEMAP ))
tr.frametime = gp_cl->time - gp_cl->oldtime;
else tr.frametime = 0.0;
// begin a new frame
tr.framecount++;
tr.dlightframecount = R_PushDlights( WORLDMODEL, tr.framecount );
R_SetupFrustum();
R_SetupFrame();
R_SetupGL( true );
R_Clear( ~0 );
R_MarkLeaves();
R_DrawFog ();
if( FBitSet( RI.rvp.flags, RF_DRAW_WORLD ))
R_AnimateRipples();
R_CheckGLFog();
R_DrawWorld();
R_CheckFog();
gEngfuncs.CL_ExtraUpdate (); // don't let sound get messed up if going slow
R_DrawEntitiesOnList();
R_DrawWaterSurfaces();
}
void R_GammaChanged( qboolean do_reset_gamma )
{
if( do_reset_gamma )
{
// paranoia cubemap rendering
if( gEngfuncs.drawFuncs->GL_BuildLightmaps )
gEngfuncs.drawFuncs->GL_BuildLightmaps( );
}
else
{
glConfig.softwareGammaUpdate = true;
GL_RebuildLightmaps();
glConfig.softwareGammaUpdate = false;
}
}
static void R_CheckCvars( void )
{
qboolean rebuild = false;
if( FBitSet( gl_overbright.flags, FCVAR_CHANGED ))
{
ClearBits( gl_overbright.flags, FCVAR_CHANGED );
rebuild = true;
}
if( FBitSet( r_vbo.flags, FCVAR_CHANGED ))
{
ClearBits( r_vbo.flags, FCVAR_CHANGED );
R_EnableVBO( r_vbo.value ? true : false );
if( R_HasEnabledVBO( ))
R_GenerateVBO();
if( gl_overbright.value )
rebuild = true;
}
if( FBitSet( r_vbo_overbrightmode.flags, FCVAR_CHANGED ) && gl_overbright.value )
{
ClearBits( r_vbo_overbrightmode.flags, FCVAR_CHANGED );
rebuild = true;
}
if( rebuild )
R_GammaChanged( false );
}
/*
===============
R_BeginFrame
===============
*/
void R_BeginFrame( qboolean clearScene )
{
glConfig.softwareGammaUpdate = false; // in case of possible fails
if(( gl_clear->value || ENGINE_GET_PARM( PARM_DEV_OVERVIEW )) &&
clearScene && ENGINE_GET_PARM( PARM_CONNSTATE ) != ca_cinematic )
{
pglClear( GL_COLOR_BUFFER_BIT );
}
R_CheckCvars();
R_Set2DMode( true );
// draw buffer stuff
pglDrawBuffer( GL_BACK );
// update texture parameters
if( FBitSet( gl_texture_nearest.flags|gl_lightmap_nearest.flags|gl_texture_anisotropy.flags|gl_texture_lodbias.flags, FCVAR_CHANGED ))
R_SetTextureParameters();
gEngfuncs.CL_ExtraUpdate ();
}
/*
===============
R_SetupRefParams
set initial params for renderer
===============
*/
void R_SetupRefParams( const ref_viewpass_t *rvp )
{
RI.rvp = *rvp;
RI.farClip = 0;
}
/*
===============
R_RenderFrame
===============
*/
void R_RenderFrame( const ref_viewpass_t *rvp )
{
if( r_norefresh->value )
return;
// setup the initial render params
R_SetupRefParams( rvp );
if( gl_finish.value && FBitSet( RI.rvp.flags, RF_DRAW_WORLD ))
pglFinish();
// completely override rendering
if( gEngfuncs.drawFuncs->GL_RenderFrame != NULL )
{
tr.fCustomRendering = true;
if( gEngfuncs.drawFuncs->GL_RenderFrame( rvp ))
{
R_GatherPlayerLight( tr.viewent );
tr.realframecount++;
tr.fResetVis = true;
return;
}
}
tr.fCustomRendering = false;
if( !FBitSet( RI.rvp.flags, RF_ONLY_CLIENTDRAW ))
R_RunViewmodelEvents();
tr.realframecount++; // right called after viewmodel events
R_RenderScene();
return;
}
/*
===============
R_EndFrame
===============
*/
void R_EndFrame( void )
{
#if XASH_PSVITA
VGL_ShimEndFrame();
#endif
#if !defined( XASH_GL_STATIC )
GL2_ShimEndFrame();
#endif
// flush any remaining 2D bits
R_Set2DMode( false );
gEngfuncs.GL_SwapBuffers();
}
/*
===============
R_DrawCubemapView
===============
*/
void R_DrawCubemapView( const vec3_t origin, const vec3_t angles, int size )
{
ref_viewpass_t rvp;
// basic params
rvp.flags = rvp.viewentity = 0;
SetBits( rvp.flags, RF_DRAW_WORLD );
SetBits( rvp.flags, RF_DRAW_CUBEMAP );
rvp.viewport[0] = rvp.viewport[1] = 0;
rvp.viewport[2] = rvp.viewport[3] = size;
rvp.fov_x = rvp.fov_y = 90.0f; // this is a final fov value
// setup origin & angles
VectorCopy( origin, rvp.vieworigin );
VectorCopy( angles, rvp.viewangles );
R_RenderFrame( &rvp );
RI.viewleaf = NULL; // force markleafs next frame
}
/*
===============
CL_FxBlend
===============
*/
int CL_FxBlend( cl_entity_t *e )
{
int blend = 0;
float offset = ((int)e->index ) * 363.0f; // Use ent index to de-sync these fx
switch( e->curstate.renderfx )
{
case kRenderFxPulseSlowWide:
blend = e->curstate.renderamt + 0x40 * sin( gp_cl->time * 2 + offset );
break;
case kRenderFxPulseFastWide:
blend = e->curstate.renderamt + 0x40 * sin( gp_cl->time * 8 + offset );
break;
case kRenderFxPulseSlow:
blend = e->curstate.renderamt + 0x10 * sin( gp_cl->time * 2 + offset );
break;
case kRenderFxPulseFast:
blend = e->curstate.renderamt + 0x10 * sin( gp_cl->time * 8 + offset );
break;
case kRenderFxFadeSlow:
if( !FBitSet( RI.rvp.flags, RF_DRAW_CUBEMAP ))
{
if( e->curstate.renderamt > 0 )
e->curstate.renderamt -= 1;
else e->curstate.renderamt = 0;
}
blend = e->curstate.renderamt;
break;
case kRenderFxFadeFast:
if( !FBitSet( RI.rvp.flags, RF_DRAW_CUBEMAP ))
{
if( e->curstate.renderamt > 3 )
e->curstate.renderamt -= 4;
else e->curstate.renderamt = 0;
}
blend = e->curstate.renderamt;
break;
case kRenderFxSolidSlow:
if( !FBitSet( RI.rvp.flags, RF_DRAW_CUBEMAP ))
{
if( e->curstate.renderamt < 255 )
e->curstate.renderamt += 1;
else e->curstate.renderamt = 255;
}
blend = e->curstate.renderamt;
break;
case kRenderFxSolidFast:
if( !FBitSet( RI.rvp.flags, RF_DRAW_CUBEMAP ))
{
if( e->curstate.renderamt < 252 )
e->curstate.renderamt += 4;
else e->curstate.renderamt = 255;
}
blend = e->curstate.renderamt;
break;
case kRenderFxStrobeSlow:
blend = 20 * sin( gp_cl->time * 4 + offset );
if( blend < 0 ) blend = 0;
else blend = e->curstate.renderamt;
break;
case kRenderFxStrobeFast:
blend = 20 * sin( gp_cl->time * 16 + offset );
if( blend < 0 ) blend = 0;
else blend = e->curstate.renderamt;
break;
case kRenderFxStrobeFaster:
blend = 20 * sin( gp_cl->time * 36 + offset );
if( blend < 0 ) blend = 0;
else blend = e->curstate.renderamt;
break;
case kRenderFxFlickerSlow:
blend = 20 * (sin( gp_cl->time * 2 ) + sin( gp_cl->time * 17 + offset ));
if( blend < 0 ) blend = 0;
else blend = e->curstate.renderamt;
break;
case kRenderFxFlickerFast:
blend = 20 * (sin( gp_cl->time * 16 ) + sin( gp_cl->time * 23 + offset ));
if( blend < 0 ) blend = 0;
else blend = e->curstate.renderamt;
break;
case kRenderFxHologram:
case kRenderFxDistort:
{
vec3_t tmp = Vec3( e->origin );
VectorSubtract( tmp, RI.rvp.vieworigin, tmp );
float dist = DotProduct( tmp, RI.vforward );
// turn off distance fade
if( e->curstate.renderfx == kRenderFxDistort )
dist = 1;
if( dist <= 0 )
{
blend = 0;
}
else
{
e->curstate.renderamt = 180;
if( dist <= 100 ) blend = e->curstate.renderamt;
else blend = (int) ((1.0f - ( dist - 100 ) * ( 1.0f / 400.0f )) * e->curstate.renderamt );
blend += gEngfuncs.COM_RandomLong( -32, 31 );
}
break;
}
default:
blend = e->curstate.renderamt;
break;
}
blend = bound( 0, blend, 255 );
return blend;
}