mirror of
https://github.com/Q3Rally-Team/q3rally.git
synced 2024-11-29 23:22:55 +00:00
3b4f4cdfa9
Some revision messages: Cache servers for each master server in q3_ui, otherwise servers from last updated master for shown for all Internet# sources. Play correct team sounds when in spectator mode and following a player. Check last listener number instead of clc.clientNum in S_AL_HearingThroughEntity so sound work correctly when spectate following a client. (Related to bug 5741.) When in third person, don't play player's sounds as full volume in Base sound system. OpenAL already does this. (Related to bug 5741.) really fix the confusion with game entity and refentity numbers to further reduce confusion, rename constants like MAX_ENTITIES to MAX_REFENTITIES Added Rend2, an alternate renderer. (Bug #4358) Fix restoring fs_game when default.cfg is missing. Fix restoring old fs_game upon leaving a server. Patch by Ensiform. Change more operator commands to require sv_running to be usable. Patch by Ensiform. Fix some "> MAX_*" to be ">= MAX_*". Fix follow command to find clients whose name begins with a number. Fix up "gc" command, make it more like "tell". Based on patch by Ensiform. Add usage messages for gc, tell, vtell, and votell commands. Check player names in gc, tell, vtell, and votell commands. #5799 - Change messagemode text box to display colors like in console input box. Improve "play" command, based on a patch from Ensiform. Check for invalid filename in OpenAL's RegisterSound function. Changed Base sound system to warn not error when sound filename is empty or too long. Remove references to non-existent functions CM_MarkFragments and CM_LerpTag.
534 lines
14 KiB
C
534 lines
14 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1999-2005 Id Software, Inc.
|
|
|
|
This file is part of Quake III Arena source code.
|
|
|
|
Quake III Arena source code 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 2 of the License,
|
|
or (at your option) any later version.
|
|
|
|
Quake III Arena source code 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.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Quake III Arena source code; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "tr_local.h"
|
|
|
|
int r_firstSceneDrawSurf;
|
|
|
|
int r_numdlights;
|
|
int r_firstSceneDlight;
|
|
|
|
int r_numentities;
|
|
int r_firstSceneEntity;
|
|
|
|
int r_numpolys;
|
|
int r_firstScenePoly;
|
|
|
|
int r_numpolyverts;
|
|
|
|
|
|
/*
|
|
====================
|
|
R_ToggleSmpFrame
|
|
|
|
====================
|
|
*/
|
|
void R_ToggleSmpFrame( void ) {
|
|
if ( r_smp->integer ) {
|
|
// use the other buffers next frame, because another CPU
|
|
// may still be rendering into the current ones
|
|
tr.smpFrame ^= 1;
|
|
} else {
|
|
tr.smpFrame = 0;
|
|
}
|
|
|
|
backEndData[tr.smpFrame]->commands.used = 0;
|
|
|
|
r_firstSceneDrawSurf = 0;
|
|
|
|
r_numdlights = 0;
|
|
r_firstSceneDlight = 0;
|
|
|
|
r_numentities = 0;
|
|
r_firstSceneEntity = 0;
|
|
|
|
r_numpolys = 0;
|
|
r_firstScenePoly = 0;
|
|
|
|
r_numpolyverts = 0;
|
|
}
|
|
|
|
|
|
/*
|
|
====================
|
|
RE_ClearScene
|
|
|
|
====================
|
|
*/
|
|
void RE_ClearScene( void ) {
|
|
r_firstSceneDlight = r_numdlights;
|
|
r_firstSceneEntity = r_numentities;
|
|
r_firstScenePoly = r_numpolys;
|
|
}
|
|
|
|
/*
|
|
===========================================================================
|
|
|
|
DISCRETE POLYS
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
/*
|
|
=====================
|
|
R_AddPolygonSurfaces
|
|
|
|
Adds all the scene's polys into this view's drawsurf list
|
|
=====================
|
|
*/
|
|
void R_AddPolygonSurfaces( void ) {
|
|
int i;
|
|
shader_t *sh;
|
|
srfPoly_t *poly;
|
|
// JBravo: Fog fixes
|
|
int fogMask;
|
|
|
|
tr.currentEntityNum = REFENTITYNUM_WORLD;
|
|
tr.shiftedEntityNum = tr.currentEntityNum << QSORT_REFENTITYNUM_SHIFT;
|
|
fogMask = -((tr.refdef.rdflags & RDF_NOFOG) == 0);
|
|
|
|
for ( i = 0, poly = tr.refdef.polys; i < tr.refdef.numPolys ; i++, poly++ ) {
|
|
sh = R_GetShaderByHandle( poly->hShader );
|
|
R_AddDrawSurf( ( void * )poly, sh, poly->fogIndex & fogMask, qfalse, qfalse );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
RE_AddPolyToScene
|
|
|
|
=====================
|
|
*/
|
|
void RE_AddPolyToScene( qhandle_t hShader, int numVerts, const polyVert_t *verts, int numPolys ) {
|
|
srfPoly_t *poly;
|
|
int i, j;
|
|
int fogIndex;
|
|
fog_t *fog;
|
|
vec3_t bounds[2];
|
|
|
|
if ( !tr.registered ) {
|
|
return;
|
|
}
|
|
|
|
if ( !hShader ) {
|
|
// This isn't a useful warning, and an hShader of zero isn't a null shader, it's
|
|
// the default shader.
|
|
//ri.Printf( PRINT_WARNING, "WARNING: RE_AddPolyToScene: NULL poly shader\n");
|
|
//return;
|
|
}
|
|
|
|
for ( j = 0; j < numPolys; j++ ) {
|
|
if ( r_numpolyverts + numVerts > max_polyverts || r_numpolys >= max_polys ) {
|
|
/*
|
|
NOTE TTimo this was initially a PRINT_WARNING
|
|
but it happens a lot with high fighting scenes and particles
|
|
since we don't plan on changing the const and making for room for those effects
|
|
simply cut this message to developer only
|
|
*/
|
|
ri.Printf( PRINT_DEVELOPER, "WARNING: RE_AddPolyToScene: r_max_polys or r_max_polyverts reached\n");
|
|
return;
|
|
}
|
|
|
|
poly = &backEndData[tr.smpFrame]->polys[r_numpolys];
|
|
poly->surfaceType = SF_POLY;
|
|
poly->hShader = hShader;
|
|
poly->numVerts = numVerts;
|
|
poly->verts = &backEndData[tr.smpFrame]->polyVerts[r_numpolyverts];
|
|
|
|
Com_Memcpy( poly->verts, &verts[numVerts*j], numVerts * sizeof( *verts ) );
|
|
|
|
if ( glConfig.hardwareType == GLHW_RAGEPRO ) {
|
|
poly->verts->modulate[0] = 255;
|
|
poly->verts->modulate[1] = 255;
|
|
poly->verts->modulate[2] = 255;
|
|
poly->verts->modulate[3] = 255;
|
|
}
|
|
// done.
|
|
r_numpolys++;
|
|
r_numpolyverts += numVerts;
|
|
|
|
// if no world is loaded
|
|
if ( tr.world == NULL ) {
|
|
fogIndex = 0;
|
|
}
|
|
// see if it is in a fog volume
|
|
else if ( tr.world->numfogs == 1 ) {
|
|
fogIndex = 0;
|
|
} else {
|
|
// find which fog volume the poly is in
|
|
VectorCopy( poly->verts[0].xyz, bounds[0] );
|
|
VectorCopy( poly->verts[0].xyz, bounds[1] );
|
|
for ( i = 1 ; i < poly->numVerts ; i++ ) {
|
|
AddPointToBounds( poly->verts[i].xyz, bounds[0], bounds[1] );
|
|
}
|
|
for ( fogIndex = 1 ; fogIndex < tr.world->numfogs ; fogIndex++ ) {
|
|
fog = &tr.world->fogs[fogIndex];
|
|
if ( bounds[1][0] >= fog->bounds[0][0]
|
|
&& bounds[1][1] >= fog->bounds[0][1]
|
|
&& bounds[1][2] >= fog->bounds[0][2]
|
|
&& bounds[0][0] <= fog->bounds[1][0]
|
|
&& bounds[0][1] <= fog->bounds[1][1]
|
|
&& bounds[0][2] <= fog->bounds[1][2] ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( fogIndex == tr.world->numfogs ) {
|
|
fogIndex = 0;
|
|
}
|
|
}
|
|
poly->fogIndex = fogIndex;
|
|
}
|
|
}
|
|
|
|
|
|
//=================================================================================
|
|
|
|
|
|
/*
|
|
=====================
|
|
RE_AddRefEntityToScene
|
|
|
|
=====================
|
|
*/
|
|
void RE_AddRefEntityToScene( const refEntity_t *ent ) {
|
|
#ifdef REACTION
|
|
// JBravo: Mirrored models
|
|
vec3_t cross;
|
|
#endif
|
|
|
|
if ( !tr.registered ) {
|
|
return;
|
|
}
|
|
if ( r_numentities >= MAX_REFENTITIES ) {
|
|
ri.Printf(PRINT_DEVELOPER, "RE_AddRefEntityToScene: Dropping refEntity, reached MAX_REFENTITIES\n");
|
|
return;
|
|
}
|
|
if ( Q_isnan(ent->origin[0]) || Q_isnan(ent->origin[1]) || Q_isnan(ent->origin[2]) ) {
|
|
static qboolean firstTime = qtrue;
|
|
if (firstTime) {
|
|
firstTime = qfalse;
|
|
ri.Printf( PRINT_WARNING, "RE_AddRefEntityToScene passed a refEntity which has an origin with a NaN component\n");
|
|
}
|
|
return;
|
|
}
|
|
if ( (int)ent->reType < 0 || ent->reType >= RT_MAX_REF_ENTITY_TYPE ) {
|
|
ri.Error( ERR_DROP, "RE_AddRefEntityToScene: bad reType %i", ent->reType );
|
|
}
|
|
|
|
backEndData[tr.smpFrame]->entities[r_numentities].e = *ent;
|
|
backEndData[tr.smpFrame]->entities[r_numentities].lightingCalculated = qfalse;
|
|
|
|
#ifdef REACTION
|
|
// JBravo: Mirrored models
|
|
CrossProduct(ent->axis[0], ent->axis[1], cross);
|
|
backEndData[tr.smpFrame]->entities[r_numentities].mirrored = (DotProduct(ent->axis[2], cross) < 0.f);
|
|
#endif
|
|
|
|
r_numentities++;
|
|
}
|
|
|
|
|
|
/*
|
|
=====================
|
|
RE_AddDynamicLightToScene
|
|
|
|
=====================
|
|
*/
|
|
void RE_AddDynamicLightToScene( const vec3_t org, float intensity, float r, float g, float b, int additive ) {
|
|
dlight_t *dl;
|
|
|
|
if ( !tr.registered ) {
|
|
return;
|
|
}
|
|
if ( r_numdlights >= MAX_DLIGHTS ) {
|
|
return;
|
|
}
|
|
if ( intensity <= 0 ) {
|
|
return;
|
|
}
|
|
// these cards don't have the correct blend mode
|
|
if ( glConfig.hardwareType == GLHW_RIVA128 || glConfig.hardwareType == GLHW_PERMEDIA2 ) {
|
|
return;
|
|
}
|
|
dl = &backEndData[tr.smpFrame]->dlights[r_numdlights++];
|
|
VectorCopy (org, dl->origin);
|
|
dl->radius = intensity;
|
|
dl->color[0] = r;
|
|
dl->color[1] = g;
|
|
dl->color[2] = b;
|
|
dl->additive = additive;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
RE_AddLightToScene
|
|
|
|
=====================
|
|
*/
|
|
void RE_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) {
|
|
RE_AddDynamicLightToScene( org, intensity, r, g, b, qfalse );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
RE_AddAdditiveLightToScene
|
|
|
|
=====================
|
|
*/
|
|
void RE_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ) {
|
|
RE_AddDynamicLightToScene( org, intensity, r, g, b, qtrue );
|
|
}
|
|
|
|
/*
|
|
@@@@@@@@@@@@@@@@@@@@@
|
|
RE_RenderScene
|
|
|
|
Draw a 3D view into a part of the window, then return
|
|
to 2D drawing.
|
|
|
|
Rendering a scene may require multiple views to be rendered
|
|
to handle mirrors,
|
|
@@@@@@@@@@@@@@@@@@@@@
|
|
*/
|
|
void RE_RenderScene( const refdef_t *fd ) {
|
|
viewParms_t parms;
|
|
int startTime;
|
|
|
|
if ( !tr.registered ) {
|
|
return;
|
|
}
|
|
GLimp_LogComment( "====== RE_RenderScene =====\n" );
|
|
|
|
if ( r_norefresh->integer ) {
|
|
return;
|
|
}
|
|
|
|
startTime = ri.Milliseconds();
|
|
|
|
if (!tr.world && !( fd->rdflags & RDF_NOWORLDMODEL ) ) {
|
|
ri.Error (ERR_DROP, "R_RenderScene: NULL worldmodel");
|
|
}
|
|
|
|
Com_Memcpy( tr.refdef.text, fd->text, sizeof( tr.refdef.text ) );
|
|
|
|
tr.refdef.x = fd->x;
|
|
tr.refdef.y = fd->y;
|
|
tr.refdef.width = fd->width;
|
|
tr.refdef.height = fd->height;
|
|
tr.refdef.fov_x = fd->fov_x;
|
|
tr.refdef.fov_y = fd->fov_y;
|
|
|
|
VectorCopy( fd->vieworg, tr.refdef.vieworg );
|
|
VectorCopy( fd->viewaxis[0], tr.refdef.viewaxis[0] );
|
|
VectorCopy( fd->viewaxis[1], tr.refdef.viewaxis[1] );
|
|
VectorCopy( fd->viewaxis[2], tr.refdef.viewaxis[2] );
|
|
|
|
tr.refdef.time = fd->time;
|
|
tr.refdef.rdflags = fd->rdflags;
|
|
|
|
// copy the areamask data over and note if it has changed, which
|
|
// will force a reset of the visible leafs even if the view hasn't moved
|
|
tr.refdef.areamaskModified = qfalse;
|
|
if ( ! (tr.refdef.rdflags & RDF_NOWORLDMODEL) ) {
|
|
int areaDiff;
|
|
int i;
|
|
|
|
// compare the area bits
|
|
areaDiff = 0;
|
|
for (i = 0 ; i < MAX_MAP_AREA_BYTES/4 ; i++) {
|
|
areaDiff |= ((int *)tr.refdef.areamask)[i] ^ ((int *)fd->areamask)[i];
|
|
((int *)tr.refdef.areamask)[i] = ((int *)fd->areamask)[i];
|
|
}
|
|
|
|
if ( areaDiff ) {
|
|
// a door just opened or something
|
|
tr.refdef.areamaskModified = qtrue;
|
|
}
|
|
}
|
|
|
|
tr.refdef.sunDir[3] = 0.0f;
|
|
tr.refdef.sunCol[3] = 1.0f;
|
|
tr.refdef.sunAmbCol[3] = 1.0f;
|
|
|
|
VectorCopy(tr.sunDirection, tr.refdef.sunDir);
|
|
if ( (tr.refdef.rdflags & RDF_NOWORLDMODEL) || !(r_depthPrepass->value) ){
|
|
tr.refdef.colorScale = 1.0f;
|
|
VectorSet(tr.refdef.sunCol, 0, 0, 0);
|
|
VectorSet(tr.refdef.sunAmbCol, 0, 0, 0);
|
|
}
|
|
else if (r_forceSun->integer == 1)
|
|
{
|
|
float scale = pow(2, r_mapOverBrightBits->integer - tr.overbrightBits - 8);
|
|
tr.refdef.colorScale = r_forceSunMapLightScale->value;
|
|
VectorScale(tr.sunLight, scale * r_forceSunLightScale->value, tr.refdef.sunCol);
|
|
VectorScale(tr.sunLight, scale * r_forceSunAmbientScale->value, tr.refdef.sunAmbCol);
|
|
}
|
|
else
|
|
{
|
|
float scale = pow(2, r_mapOverBrightBits->integer - tr.overbrightBits - 8);
|
|
tr.refdef.colorScale = tr.mapLightScale;
|
|
VectorScale(tr.sunLight, scale, tr.refdef.sunCol);
|
|
VectorScale(tr.sunAmbient, scale, tr.refdef.sunAmbCol);
|
|
}
|
|
|
|
if (r_forceAutoExposure->integer)
|
|
{
|
|
tr.refdef.autoExposureMinMax[0] = r_forceAutoExposureMin->value;
|
|
tr.refdef.autoExposureMinMax[1] = r_forceAutoExposureMax->value;
|
|
}
|
|
else
|
|
{
|
|
tr.refdef.autoExposureMinMax[0] = tr.autoExposureMinMax[0];
|
|
tr.refdef.autoExposureMinMax[1] = tr.autoExposureMinMax[1];
|
|
}
|
|
|
|
if (r_forceToneMap->integer)
|
|
{
|
|
tr.refdef.toneMinAvgMaxLinear[0] = pow(2, r_forceToneMapMin->value);
|
|
tr.refdef.toneMinAvgMaxLinear[1] = pow(2, r_forceToneMapAvg->value);
|
|
tr.refdef.toneMinAvgMaxLinear[2] = pow(2, r_forceToneMapMax->value);
|
|
}
|
|
else
|
|
{
|
|
tr.refdef.toneMinAvgMaxLinear[0] = pow(2, tr.toneMinAvgMaxLevel[0]);
|
|
tr.refdef.toneMinAvgMaxLinear[1] = pow(2, tr.toneMinAvgMaxLevel[1]);
|
|
tr.refdef.toneMinAvgMaxLinear[2] = pow(2, tr.toneMinAvgMaxLevel[2]);
|
|
}
|
|
|
|
//#ifdef REACTION
|
|
// Makro - copy exta info if present
|
|
if (fd->rdflags & RDF_EXTRA) {
|
|
const refdefex_t* extra = (const refdefex_t*) (fd+1);
|
|
|
|
tr.refdef.blurFactor = extra->blurFactor;
|
|
|
|
if (fd->rdflags & RDF_SUNLIGHT)
|
|
{
|
|
VectorCopy(extra->sunDir, tr.refdef.sunDir);
|
|
VectorCopy(extra->sunCol, tr.refdef.sunCol);
|
|
VectorCopy(extra->sunAmbCol, tr.refdef.sunAmbCol);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tr.refdef.blurFactor = 0.0f;
|
|
}
|
|
//#endif
|
|
|
|
// derived info
|
|
|
|
tr.refdef.floatTime = tr.refdef.time * 0.001f;
|
|
|
|
tr.refdef.numDrawSurfs = r_firstSceneDrawSurf;
|
|
tr.refdef.drawSurfs = backEndData[tr.smpFrame]->drawSurfs;
|
|
|
|
tr.refdef.num_entities = r_numentities - r_firstSceneEntity;
|
|
tr.refdef.entities = &backEndData[tr.smpFrame]->entities[r_firstSceneEntity];
|
|
|
|
tr.refdef.num_dlights = r_numdlights - r_firstSceneDlight;
|
|
tr.refdef.dlights = &backEndData[tr.smpFrame]->dlights[r_firstSceneDlight];
|
|
|
|
tr.refdef.numPolys = r_numpolys - r_firstScenePoly;
|
|
tr.refdef.polys = &backEndData[tr.smpFrame]->polys[r_firstScenePoly];
|
|
|
|
tr.refdef.num_pshadows = 0;
|
|
tr.refdef.pshadows = &backEndData[tr.smpFrame]->pshadows[0];
|
|
|
|
// turn off dynamic lighting globally by clearing all the
|
|
// dlights if it needs to be disabled or if vertex lighting is enabled
|
|
if ( r_dynamiclight->integer == 0 ||
|
|
r_vertexLight->integer == 1 ||
|
|
glConfig.hardwareType == GLHW_PERMEDIA2 ) {
|
|
tr.refdef.num_dlights = 0;
|
|
}
|
|
|
|
// a single frame may have multiple scenes draw inside it --
|
|
// a 3D game view, 3D status bar renderings, 3D menus, etc.
|
|
// They need to be distinguished by the light flare code, because
|
|
// the visibility state for a given surface may be different in
|
|
// each scene / view.
|
|
tr.frameSceneNum++;
|
|
tr.sceneCount++;
|
|
|
|
// SmileTheory: playing with shadow mapping
|
|
if (!( fd->rdflags & RDF_NOWORLDMODEL ) && tr.refdef.num_dlights && r_dlightMode->integer >= 2)
|
|
{
|
|
R_RenderDlightCubemaps(fd);
|
|
}
|
|
|
|
/* playing with more shadows */
|
|
if(glRefConfig.framebufferObject && !( fd->rdflags & RDF_NOWORLDMODEL ) && r_shadows->integer == 4)
|
|
{
|
|
R_RenderPshadowMaps(fd);
|
|
}
|
|
|
|
// playing with even more shadows
|
|
if(glRefConfig.framebufferObject && !( fd->rdflags & RDF_NOWORLDMODEL ) && (r_forceSun->integer || tr.sunShadows))
|
|
{
|
|
R_RenderSunShadowMaps(fd, 0);
|
|
R_RenderSunShadowMaps(fd, 1);
|
|
R_RenderSunShadowMaps(fd, 2);
|
|
}
|
|
|
|
// setup view parms for the initial view
|
|
//
|
|
// set up viewport
|
|
// The refdef takes 0-at-the-top y coordinates, so
|
|
// convert to GL's 0-at-the-bottom space
|
|
//
|
|
Com_Memset( &parms, 0, sizeof( parms ) );
|
|
parms.viewportX = tr.refdef.x;
|
|
parms.viewportY = glConfig.vidHeight - ( tr.refdef.y + tr.refdef.height );
|
|
parms.viewportWidth = tr.refdef.width;
|
|
parms.viewportHeight = tr.refdef.height;
|
|
parms.isPortal = qfalse;
|
|
|
|
parms.fovX = tr.refdef.fov_x;
|
|
parms.fovY = tr.refdef.fov_y;
|
|
|
|
parms.stereoFrame = tr.refdef.stereoFrame;
|
|
|
|
VectorCopy( fd->vieworg, parms.or.origin );
|
|
VectorCopy( fd->viewaxis[0], parms.or.axis[0] );
|
|
VectorCopy( fd->viewaxis[1], parms.or.axis[1] );
|
|
VectorCopy( fd->viewaxis[2], parms.or.axis[2] );
|
|
|
|
VectorCopy( fd->vieworg, parms.pvsOrigin );
|
|
|
|
if(!( fd->rdflags & RDF_NOWORLDMODEL ) && r_depthPrepass->value && ((r_forceSun->integer) || tr.sunShadows))
|
|
{
|
|
parms.flags = VPF_USESUNLIGHT;
|
|
}
|
|
|
|
R_RenderView( &parms );
|
|
|
|
if(!( fd->rdflags & RDF_NOWORLDMODEL ))
|
|
R_AddPostProcessCmd();
|
|
|
|
// the next scene rendered in this frame will tack on after this one
|
|
r_firstSceneDrawSurf = tr.refdef.numDrawSurfs;
|
|
r_firstSceneEntity = r_numentities;
|
|
r_firstSceneDlight = r_numdlights;
|
|
r_firstScenePoly = r_numpolys;
|
|
|
|
tr.frontEndMsec += ri.Milliseconds() - startTime;
|
|
}
|