/* 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 "common.h" #include "client.h" #include "gl_local.h" #include "mathlib.h" #include "library.h" #include "beamdef.h" #include "particledef.h" #include "entity_types.h" #define IsLiquidContents( cnt ) ( cnt == CONTENTS_WATER || cnt == CONTENTS_SLIME || cnt == CONTENTS_LAVA ) msurface_t *r_debug_surface; const char *r_debug_hitbox; float gldepthmin, gldepthmax; ref_params_t r_lastRefdef; ref_instance_t RI, prevRI; mleaf_t *r_viewleaf, *r_oldviewleaf; mleaf_t *r_viewleaf2, *r_oldviewleaf2; static int R_RankForRenderMode( cl_entity_t *ent ) { switch( ent->curstate.rendermode ) { case kRenderTransTexture: return 1; // draw second case kRenderTransAdd: return 2; // draw third case kRenderGlow: return 3; // must be last! } return 0; } /* =============== R_StaticEntity Static entity is the brush which has no custom origin and not rotated typically is a func_wall, func_breakable, func_ladder etc =============== */ static qboolean R_StaticEntity( cl_entity_t *ent ) { if( !gl_allow_static->integer ) return false; if( ent->curstate.rendermode != kRenderNormal ) return false; if( ent->model->type != mod_brush ) return false; if( ent->curstate.effects & ( EF_NOREFLECT|EF_REFLECTONLY )) return false; if( ent->curstate.frame || ent->model->flags & MODEL_CONVEYOR ) return false; if( ent->curstate.scale ) // waveheight specified return false; if( !VectorIsNull( ent->origin ) || !VectorIsNull( ent->angles )) return false; return true; } /* =============== R_FollowEntity Follow entity is attached to another studiomodel and used last cached bones from parent =============== */ static qboolean R_FollowEntity( cl_entity_t *ent ) { if( ent->model->type != mod_studio ) return false; if( ent->curstate.movetype != MOVETYPE_FOLLOW ) return false; if( ent->curstate.aiment <= 0 ) return false; return true; } /* =============== R_OpaqueEntity Opaque entity can be brush or studio model but sprite =============== */ static qboolean R_OpaqueEntity( cl_entity_t *ent ) { if( ent->curstate.rendermode == kRenderNormal ) return true; if( ent->model->type == mod_sprite ) return false; if( ent->curstate.rendermode == kRenderTransAlpha ) return true; return false; } /* =============== R_SolidEntityCompare Sorting opaque entities by model type =============== */ static int R_SolidEntityCompare( const cl_entity_t **a, const cl_entity_t **b ) { cl_entity_t *ent1, *ent2; ent1 = (cl_entity_t *)*a; ent2 = (cl_entity_t *)*b; if( ent1->model->type > ent2->model->type ) return 1; if( ent1->model->type < ent2->model->type ) return -1; return 0; } /* =============== R_TransEntityCompare Sorting translucent entities by rendermode then by distance =============== */ static int R_TransEntityCompare( const cl_entity_t **a, const cl_entity_t **b ) { cl_entity_t *ent1, *ent2; vec3_t vecLen, org; float len1, len2; ent1 = (cl_entity_t *)*a; ent2 = (cl_entity_t *)*b; // now sort by rendermode if( R_RankForRenderMode( ent1 ) > R_RankForRenderMode( ent2 )) return 1; if( R_RankForRenderMode( ent1 ) < R_RankForRenderMode( ent2 )) return -1; // then by distance if( ent1->model->type == mod_brush ) { VectorAverage( ent1->model->mins, ent1->model->maxs, org ); VectorAdd( ent1->origin, org, org ); VectorSubtract( RI.vieworg, org, vecLen ); } else VectorSubtract( RI.vieworg, ent1->origin, vecLen ); len1 = VectorLength( vecLen ); if( ent2->model->type == mod_brush ) { VectorAverage( ent2->model->mins, ent2->model->maxs, org ); VectorAdd( ent2->origin, org, org ); VectorSubtract( RI.vieworg, org, vecLen ); } else VectorSubtract( RI.vieworg, ent2->origin, vecLen ); len2 = VectorLength( vecLen ); if( len1 > len2 ) return -1; if( len1 < len2 ) return 1; return 0; } /* =============== R_WorldToScreen Convert a given point from world into screen space =============== */ qboolean R_WorldToScreen( const vec3_t point, vec3_t screen ) { matrix4x4 worldToScreen; qboolean behind; float w; if( !point || !screen ) return false; 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]; 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 if( w < 0.001f ) { behind = true; screen[0] *= 100000; screen[1] *= 100000; } else { float invw = 1.0f / w; behind = false; screen[0] *= invw; screen[1] *= invw; } return behind; } /* =============== R_ScreenToWorld Convert a given point from screen into world space =============== */ void R_ScreenToWorld( const vec3_t screen, vec3_t point ) { matrix4x4 screenToWorld; float w; if( !point || !screen ) return; 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]; 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_ComputeFxBlend =============== */ int R_ComputeFxBlend( cl_entity_t *e ) { int blend = 0, renderAmt; float offset, dist; vec3_t tmp; offset = ((int)e->index ) * 363.0f; // Use ent index to de-sync these fx renderAmt = e->curstate.renderamt; switch( e->curstate.renderfx ) { case kRenderFxPulseSlowWide: blend = renderAmt + 0x40 * sin( RI.refdef.time * 2 + offset ); break; case kRenderFxPulseFastWide: blend = renderAmt + 0x40 * sin( RI.refdef.time * 8 + offset ); break; case kRenderFxPulseSlow: blend = renderAmt + 0x10 * sin( RI.refdef.time * 2 + offset ); break; case kRenderFxPulseFast: blend = renderAmt + 0x10 * sin( RI.refdef.time * 8 + offset ); break; // JAY: HACK for now -- not time based case kRenderFxFadeSlow: if( renderAmt > 0 ) renderAmt -= 1; else renderAmt = 0; blend = renderAmt; break; case kRenderFxFadeFast: if( renderAmt > 3 ) renderAmt -= 4; else renderAmt = 0; blend = renderAmt; break; case kRenderFxSolidSlow: if( renderAmt < 255 ) renderAmt += 1; else renderAmt = 255; blend = renderAmt; break; case kRenderFxSolidFast: if( renderAmt < 252 ) renderAmt += 4; else renderAmt = 255; blend = renderAmt; break; case kRenderFxStrobeSlow: blend = 20 * sin( RI.refdef.time * 4 + offset ); if( blend < 0 ) blend = 0; else blend = renderAmt; break; case kRenderFxStrobeFast: blend = 20 * sin( RI.refdef.time * 16 + offset ); if( blend < 0 ) blend = 0; else blend = renderAmt; break; case kRenderFxStrobeFaster: blend = 20 * sin( RI.refdef.time * 36 + offset ); if( blend < 0 ) blend = 0; else blend = renderAmt; break; case kRenderFxFlickerSlow: blend = 20 * (sin( RI.refdef.time * 2 ) + sin( RI.refdef.time * 17 + offset )); if( blend < 0 ) blend = 0; else blend = renderAmt; break; case kRenderFxFlickerFast: blend = 20 * (sin( RI.refdef.time * 16 ) + sin( RI.refdef.time * 23 + offset )); if( blend < 0 ) blend = 0; else blend = renderAmt; break; case kRenderFxHologram: case kRenderFxDistort: VectorCopy( e->origin, tmp ); VectorSubtract( tmp, RI.refdef.vieworg, tmp ); dist = DotProduct( tmp, RI.refdef.forward ); // Turn off distance fade if( e->curstate.renderfx == kRenderFxDistort ) dist = 1; if( dist <= 0 ) { blend = 0; } else { renderAmt = 180; if( dist <= 100 ) blend = renderAmt; else blend = (int) ((1.0f - ( dist - 100 ) * ( 1.0f / 400.0f )) * renderAmt ); blend += Com_RandomLong( -32, 31 ); } break; case kRenderFxGlowShell: // safe current renderamt because it's shell scale! case kRenderFxDeadPlayer: // safe current renderamt because it's player index! blend = renderAmt; break; case kRenderFxNone: case kRenderFxClampMinScale: default: if( e->curstate.rendermode == kRenderNormal ) blend = 255; else blend = renderAmt; break; } if( RI.currentmodel->type != mod_brush ) { // NOTE: never pass sprites with rendercolor '0 0 0' it's a stupid Valve Hammer Editor bug if( !e->curstate.rendercolor.r && !e->curstate.rendercolor.g && !e->curstate.rendercolor.b ) e->curstate.rendercolor.r = e->curstate.rendercolor.g = e->curstate.rendercolor.b = 255; } // apply scale to studiomodels and sprites only if( e->model && e->model->type != mod_brush && !e->curstate.scale ) e->curstate.scale = 1.0f; blend = bound( 0, blend, 255 ); return blend; } /* =============== R_ClearScene =============== */ void R_ClearScene( void ) { tr.num_solid_entities = tr.num_trans_entities = 0; tr.num_static_entities = tr.num_mirror_entities = 0; tr.num_child_entities = 0; } /* =============== R_AddEntity =============== */ qboolean R_AddEntity( struct cl_entity_s *clent, int entityType ) { if( !r_drawentities->integer ) return false; // not allow to drawing if( !clent || !clent->model ) return false; // if set to invisible, skip if( clent->curstate.effects & EF_NODRAW ) return false; // done if( clent->curstate.rendermode != kRenderNormal && clent->curstate.renderamt <= 0.0f ) return true; // done clent->curstate.entityType = entityType; if( entityType == ET_FRAGMENTED ) r_stats.c_client_ents++; if( R_FollowEntity( clent )) { // follow entity if( tr.num_child_entities >= MAX_VISIBLE_PACKET ) return false; tr.child_entities[tr.num_child_entities] = clent; tr.num_child_entities++; return true; } if( R_OpaqueEntity( clent )) { if( R_StaticEntity( clent )) { // opaque static if( tr.num_static_entities >= MAX_VISIBLE_PACKET ) return false; tr.static_entities[tr.num_static_entities] = clent; tr.num_static_entities++; } else { // opaque moving if( tr.num_solid_entities >= MAX_VISIBLE_PACKET ) return false; tr.solid_entities[tr.num_solid_entities] = clent; tr.num_solid_entities++; } } else { // translucent if( tr.num_trans_entities >= MAX_VISIBLE_PACKET ) return false; tr.trans_entities[tr.num_trans_entities] = clent; tr.num_trans_entities++; } return true; } /* ============= R_Clear ============= */ static void R_Clear( int bitMask ) { int bits; if( gl_overview->integer ) pglClearColor( 0.0f, 1.0f, 0.0f, 1.0f ); // green background (Valve rules) else pglClearColor( 0.5f, 0.5f, 0.5f, 1.0f ); bits = GL_DEPTH_BUFFER_BIT; if( RI.drawWorld && r_fastsky->integer ) bits |= GL_COLOR_BUFFER_BIT; if( glState.stencilEnabled ) bits |= GL_STENCIL_BUFFER_BIT; bits &= bitMask; pglClear( bits ); // change ordering for overview if( RI.drawOrtho ) { 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( cl.worldmodel && RI.drawWorld ) return RI.refdef.movevars->zmax * 1.5f; return 2048.0f; } /* =============== R_SetupFrustumOrtho =============== */ static void R_SetupFrustumOrtho( void ) { ref_overview_t *ov = &clgame.overView; float orgOffset; int i; // 0 - left // 1 - right // 2 - down // 3 - up // 4 - farclip // 5 - nearclip // setup the near and far planes. orgOffset = DotProduct( RI.cullorigin, RI.cull_vforward ); VectorNegate( RI.cull_vforward, RI.frustum[4].normal ); RI.frustum[4].dist = -ov->zFar - orgOffset; VectorCopy( RI.cull_vforward, RI.frustum[5].normal ); RI.frustum[5].dist = ov->zNear + orgOffset; // left and right planes... orgOffset = DotProduct( RI.cullorigin, RI.cull_vright ); VectorCopy( RI.cull_vright, RI.frustum[0].normal ); RI.frustum[0].dist = ov->xLeft + orgOffset; VectorNegate( RI.cull_vright, RI.frustum[1].normal ); RI.frustum[1].dist = -ov->xRight - orgOffset; // top and buttom planes... orgOffset = DotProduct( RI.cullorigin, RI.cull_vup ); VectorCopy( RI.cull_vup, RI.frustum[3].normal ); RI.frustum[3].dist = ov->xTop + orgOffset; VectorNegate( RI.cull_vup, RI.frustum[2].normal ); RI.frustum[2].dist = -ov->xBottom - orgOffset; for( i = 0; i < 6; i++ ) { RI.frustum[i].type = PLANE_NONAXIAL; RI.frustum[i].signbits = SignbitsForPlane( RI.frustum[i].normal ); } } /* =============== R_SetupFrustum =============== */ void R_SetupFrustum( void ) { vec3_t farPoint; int i; // 0 - left // 1 - right // 2 - down // 3 - up // 4 - farclip // 5 - nearclip if( RI.drawOrtho ) { R_SetupFrustumOrtho(); return; } // rotate RI.vforward right by FOV_X/2 degrees RotatePointAroundVector( RI.frustum[0].normal, RI.cull_vup, RI.cull_vforward, -( 90 - RI.refdef.fov_x / 2 )); // rotate RI.vforward left by FOV_X/2 degrees RotatePointAroundVector( RI.frustum[1].normal, RI.cull_vup, RI.cull_vforward, 90 - RI.refdef.fov_x / 2 ); // rotate RI.vforward up by FOV_X/2 degrees RotatePointAroundVector( RI.frustum[2].normal, RI.cull_vright, RI.cull_vforward, 90 - RI.refdef.fov_y / 2 ); // rotate RI.vforward down by FOV_X/2 degrees RotatePointAroundVector( RI.frustum[3].normal, RI.cull_vright, RI.cull_vforward, -( 90 - RI.refdef.fov_y / 2 )); // negate forward vector VectorNegate( RI.cull_vforward, RI.frustum[4].normal ); for( i = 0; i < 4; i++ ) { RI.frustum[i].type = PLANE_NONAXIAL; RI.frustum[i].dist = DotProduct( RI.cullorigin, RI.frustum[i].normal ); RI.frustum[i].signbits = SignbitsForPlane( RI.frustum[i].normal ); } VectorMA( RI.cullorigin, R_GetFarClip(), RI.cull_vforward, farPoint ); RI.frustum[i].type = PLANE_NONAXIAL; RI.frustum[i].dist = DotProduct( farPoint, RI.frustum[i].normal ); RI.frustum[i].signbits = SignbitsForPlane( RI.frustum[i].normal ); } /* ============= R_SetupProjectionMatrix ============= */ static void R_SetupProjectionMatrix( const ref_params_t *fd, matrix4x4 m ) { GLdouble xMin, xMax, yMin, yMax, zNear, zFar; if( RI.drawOrtho ) { ref_overview_t *ov = &clgame.overView; Matrix4x4_CreateOrtho( m, ov->xLeft, ov->xRight, ov->xTop, ov->xBottom, ov->zNear, ov->zFar ); return; } RI.farClip = R_GetFarClip(); zNear = 4.0f; zFar = max( 256.0f, RI.farClip ); yMax = zNear * tan( fd->fov_y * M_PI / 360.0 ); yMin = -yMax; xMax = zNear * tan( fd->fov_x * M_PI / 360.0 ); xMin = -xMax; Matrix4x4_CreateProjection( m, xMax, xMin, yMax, yMin, zNear, zFar ); } /* ============= R_SetupModelviewMatrix ============= */ static void R_SetupModelviewMatrix( const ref_params_t *fd, matrix4x4 m ) { #if 0 Matrix4x4_LoadIdentity( m ); Matrix4x4_ConcatRotate( m, -90, 1, 0, 0 ); Matrix4x4_ConcatRotate( m, 90, 0, 0, 1 ); #else Matrix4x4_CreateModelview( m ); #endif Matrix4x4_ConcatRotate( m, -fd->viewangles[2], 1, 0, 0 ); Matrix4x4_ConcatRotate( m, -fd->viewangles[0], 0, 1, 0 ); Matrix4x4_ConcatRotate( m, -fd->viewangles[1], 0, 0, 1 ); Matrix4x4_ConcatTranslate( m, -fd->vieworg[0], -fd->vieworg[1], -fd->vieworg[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 == clgame.entities || R_StaticEntity( e )) { 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 == clgame.entities || R_StaticEntity( e )) { 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 ) { float height; mleaf_t *leaf; vec3_t tmp; r_oldviewleaf = r_viewleaf; r_oldviewleaf2 = r_viewleaf2; leaf = Mod_PointInLeaf( RI.pvsorigin, cl.worldmodel->nodes ); r_viewleaf2 = r_viewleaf = leaf; height = RI.waveHeight ? RI.waveHeight : 16; // check above and below so crossing solid water doesn't draw wrong if( leaf->contents == CONTENTS_EMPTY ) { // look down a bit VectorCopy( RI.pvsorigin, tmp ); tmp[2] -= height; leaf = Mod_PointInLeaf( tmp, cl.worldmodel->nodes ); if(( leaf->contents != CONTENTS_SOLID ) && ( leaf != r_viewleaf2 )) r_viewleaf2 = leaf; } else { // look up a bit VectorCopy( RI.pvsorigin, tmp ); tmp[2] += height; leaf = Mod_PointInLeaf( tmp, cl.worldmodel->nodes ); if(( leaf->contents != CONTENTS_SOLID ) && ( leaf != r_viewleaf2 )) r_viewleaf2 = leaf; } } /* =============== R_SetupFrame =============== */ static void R_SetupFrame( void ) { // build the transformation matrix for the given view angles VectorCopy( RI.refdef.vieworg, RI.vieworg ); AngleVectors( RI.refdef.viewangles, RI.vforward, RI.vright, RI.vup ); // setup viewplane dist RI.viewplanedist = DotProduct( RI.refdef.vieworg, RI.vforward ); if( !r_lockcull->integer ) { VectorCopy( RI.vforward, RI.cull_vforward ); VectorCopy( RI.vright, RI.cull_vright ); VectorCopy( RI.vup, RI.cull_vup ); } R_AnimateLight(); R_RunViewmodelEvents(); // sort opaque entities by model type to avoid drawing model shadows under alpha-surfaces qsort( tr.solid_entities, tr.num_solid_entities, sizeof( cl_entity_t* ), R_SolidEntityCompare ); if( !gl_nosort->integer ) { // sort translucents entities by rendermode and distance qsort( tr.trans_entities, tr.num_trans_entities, sizeof( cl_entity_t* ), R_TransEntityCompare ); } // current viewleaf if( RI.drawWorld ) { RI.waveHeight = RI.refdef.movevars->waveHeight * 2.0f; // set global waveheight RI.isSkyVisible = false; // unknown at this moment if(!( RI.params & RP_OLDVIEWLEAF )) R_FindViewLeaf(); } } /* ============= R_SetupGL ============= */ static void R_SetupGL( void ) { if( RI.refdef.waterlevel >= 3 ) { float f; f = sin( cl.time * 0.4f * ( M_PI * 2.7f )); RI.refdef.fov_x += f; RI.refdef.fov_y -= f; } R_SetupModelviewMatrix( &RI.refdef, RI.worldviewMatrix ); R_SetupProjectionMatrix( &RI.refdef, RI.projectionMatrix ); // if( RI.params & RP_MIRRORVIEW ) RI.projectionMatrix[0][0] = -RI.projectionMatrix[0][0]; Matrix4x4_Concat( RI.worldviewProjectionMatrix, RI.projectionMatrix, RI.worldviewMatrix ); pglScissor( RI.scissor[0], RI.scissor[1], RI.scissor[2], RI.scissor[3] ); pglViewport( RI.viewport[0], RI.viewport[1], RI.viewport[2], RI.viewport[3] ); pglMatrixMode( GL_PROJECTION ); GL_LoadMatrix( RI.projectionMatrix ); pglMatrixMode( GL_MODELVIEW ); GL_LoadMatrix( RI.worldviewMatrix ); if( RI.params & RP_CLIPPLANE ) { GLdouble clip[4]; mplane_t *p = &RI.clipPlane; clip[0] = p->normal[0]; clip[1] = p->normal[1]; clip[2] = p->normal[2]; clip[3] = -p->dist; pglClipPlane( GL_CLIP_PLANE0, clip ); pglEnable( GL_CLIP_PLANE0 ); } if( RI.params & RP_FLIPFRONTFACE ) GL_FrontFace( !glState.frontFace ); GL_Cull( GL_FRONT ); pglDisable( GL_BLEND ); pglDisable( GL_ALPHA_TEST ); pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); } /* ============= R_EndGL ============= */ static void R_EndGL( void ) { if( RI.params & RP_FLIPFRONTFACE ) GL_FrontFace( !glState.frontFace ); if( RI.params & RP_CLIPPLANE ) pglDisable( GL_CLIP_PLANE0 ); } /* ============= R_RecursiveFindWaterTexture using to find source waterleaf with watertexture to grab fog values from it ============= */ static gltexture_t *R_RecursiveFindWaterTexture( const mnode_t *node, const mnode_t *ignore, qboolean down ) { gltexture_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 ) { mleaf_t *pleaf; msurface_t **mark; int i, c; // ignore non-liquid leaves if( node->contents != CONTENTS_WATER && node->contents != CONTENTS_LAVA && node->contents != CONTENTS_SLIME ) return NULL; // find texture pleaf = (mleaf_t *)node; mark = pleaf->firstmarksurface; c = pleaf->nummarksurfaces; for( 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 if( node->children[0] && ( node->children[0] != ignore )) { tex = R_RecursiveFindWaterTexture( node->children[0], node, true ); if( tex ) return tex; } if( node->children[1] && ( node->children[1] != ignore )) { tex = R_RecursiveFindWaterTexture( node->children[1], 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 ) { cl_entity_t *ent; gltexture_t *tex; int i, cnt, count; RI.fogEnabled = false; if( RI.refdef.waterlevel < 2 || !RI.drawWorld || !r_viewleaf ) return; ent = CL_GetWaterEntity( RI.vieworg ); if( ent && ent->model && ent->model->type == mod_brush && ent->curstate.skin < 0 ) cnt = ent->curstate.skin; else cnt = r_viewleaf->contents; if( IsLiquidContents( RI.cached_contents ) && !IsLiquidContents( cnt )) { RI.cached_contents = CONTENTS_EMPTY; return; } if( RI.refdef.waterlevel < 3 ) return; if( !IsLiquidContents( RI.cached_contents ) && IsLiquidContents( cnt )) { tex = NULL; // check for water texture if( ent && ent->model && ent->model->type == mod_brush ) { msurface_t *surf; count = ent->model->nummodelsurfaces; for( i = 0, surf = &ent->model->surfaces[ent->model->firstmodelsurface]; 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( r_viewleaf->parent, NULL, false ); if( tex ) RI.cached_contents = r_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.fogCustom = false; RI.fogEnabled = true; } else { RI.fogCustom = false; RI.fogEnabled = true; } } /* ============= R_DrawFog ============= */ void R_DrawFog( void ) { if( !RI.fogEnabled || RI.refdef.onlyClientDraw ) return; pglEnable( GL_FOG ); pglFogi( GL_FOG_MODE, GL_EXP ); pglFogf( GL_FOG_DENSITY, RI.fogDensity ); pglFogfv( GL_FOG_COLOR, RI.fogColor ); pglHint( GL_FOG_HINT, GL_NICEST ); } /* ============= R_DrawEntitiesOnList ============= */ void R_DrawEntitiesOnList( void ) { int i; glState.drawTrans = false; // draw the solid submodels fog R_DrawFog (); // first draw solid entities for( i = 0; i < tr.num_solid_entities; i++ ) { if( RI.refdef.onlyClientDraw ) break; RI.currententity = tr.solid_entities[i]; RI.currentmodel = RI.currententity->model; ASSERT( RI.currententity != NULL ); ASSERT( RI.currententity->model != NULL ); RI.currententity->curstate.renderamt = R_ComputeFxBlend( RI.currententity ); switch( RI.currentmodel->type ) { case mod_brush: R_DrawBrushModel( RI.currententity ); break; case mod_studio: R_DrawStudioModel( RI.currententity ); break; case mod_sprite: R_DrawSpriteModel( RI.currententity ); break; default: break; } } if( !RI.refdef.onlyClientDraw ) { CL_DrawBeams( false ); } if( RI.drawWorld ) clgame.dllFuncs.pfnDrawNormalTriangles(); // NOTE: some mods with custom renderer may generate glErrors // so we clear it here while( pglGetError() != GL_NO_ERROR ); // don't fogging translucent surfaces if( !RI.fogCustom ) pglDisable( GL_FOG ); pglDepthMask( GL_FALSE ); glState.drawTrans = true; // then draw translucent entities for( i = 0; i < tr.num_trans_entities; i++ ) { if( RI.refdef.onlyClientDraw ) break; RI.currententity = tr.trans_entities[i]; RI.currentmodel = RI.currententity->model; ASSERT( RI.currententity != NULL ); ASSERT( RI.currententity->model != NULL ); RI.currententity->curstate.renderamt = R_ComputeFxBlend( RI.currententity ); switch( RI.currentmodel->type ) { case mod_brush: R_DrawBrushModel( RI.currententity ); break; case mod_studio: R_DrawStudioModel( RI.currententity ); break; case mod_sprite: R_DrawSpriteModel( RI.currententity ); break; default: break; } } if( RI.drawWorld ) clgame.dllFuncs.pfnDrawTransparentTriangles (); if( !RI.refdef.onlyClientDraw ) { CL_DrawBeams( true ); CL_DrawParticles(); } // NOTE: some mods with custom renderer may generate glErrors // so we clear it here while( pglGetError() != GL_NO_ERROR ); glState.drawTrans = false; pglDepthMask( GL_TRUE ); pglDisable( GL_BLEND ); // Trinity Render issues R_DrawViewModel(); CL_ExtraUpdate(); } /* ================ R_RenderScene RI.refdef must be set before the first call ================ */ void R_RenderScene( const ref_params_t *fd ) { RI.refdef = *fd; if( !cl.worldmodel && RI.drawWorld ) Host_Error( "R_RenderView: NULL worldmodel\n" ); R_PushDlights(); R_SetupFrame(); R_SetupFrustum(); R_SetupGL(); R_Clear( ~0 ); R_MarkLeaves(); R_CheckFog(); R_DrawWorld(); CL_ExtraUpdate (); // don't let sound get messed up if going slow R_DrawEntitiesOnList(); R_DrawWaterSurfaces(); R_EndGL(); } /* =============== R_BeginFrame =============== */ void R_BeginFrame( qboolean clearScene ) { if(( gl_clear->integer || gl_overview->integer ) && clearScene && cls.state != ca_cinematic ) { pglClear( GL_COLOR_BUFFER_BIT ); } // update gamma if( vid_gamma->modified ) { if( glConfig.deviceSupportsGamma ) { SCR_RebuildGammaTable(); GL_UpdateGammaRamp(); vid_gamma->modified = false; } else { BuildGammaTable( vid_gamma->value, vid_texgamma->value ); GL_RebuildLightmaps(); } } R_Set2DMode( true ); // draw buffer stuff pglDrawBuffer( GL_BACK ); // texturemode stuff // update texture parameters if( gl_texturemode->modified || gl_texture_anisotropy->modified || gl_texture_lodbias ->modified ) R_SetTextureParameters(); // swapinterval stuff GL_UpdateSwapInterval(); CL_ExtraUpdate (); } /* =============== R_RenderFrame =============== */ void R_RenderFrame( const ref_params_t *fd, qboolean drawWorld ) { if( r_norefresh->integer ) return; tr.realframecount++; if( RI.drawOrtho != gl_overview->integer ) tr.fResetVis = true; // completely override rendering if( clgame.drawFuncs.GL_RenderFrame != NULL ) { if( clgame.drawFuncs.GL_RenderFrame( fd, drawWorld )) { tr.fResetVis = true; return; } } if( drawWorld ) r_lastRefdef = *fd; RI.params = RP_NONE; RI.farClip = 0; RI.clipFlags = 15; RI.drawWorld = drawWorld; RI.thirdPerson = cl.thirdperson; RI.drawOrtho = gl_overview->integer; GL_BackendStartFrame(); if( !r_lockcull->integer ) VectorCopy( fd->vieworg, RI.cullorigin ); VectorCopy( fd->vieworg, RI.pvsorigin ); // setup scissor RI.scissor[0] = fd->viewport[0]; RI.scissor[1] = glState.height - fd->viewport[3] - fd->viewport[1]; RI.scissor[2] = fd->viewport[2]; RI.scissor[3] = fd->viewport[3]; // setup viewport RI.viewport[0] = fd->viewport[0]; RI.viewport[1] = glState.height - fd->viewport[3] - fd->viewport[1]; RI.viewport[2] = fd->viewport[2]; RI.viewport[3] = fd->viewport[3]; if( gl_finish->integer && drawWorld ) pglFinish(); if( gl_allow_mirrors->integer ) { // render mirrors R_FindMirrors( fd ); R_DrawMirrors (); } R_RenderScene( fd ); GL_BackendEndFrame(); } /* =============== R_EndFrame =============== */ void R_EndFrame( void ) { // flush any remaining 2D bits R_Set2DMode( false ); if( !pwglSwapBuffers( glw_state.hDC )) Sys_Error( "wglSwapBuffers() failed!\n" ); } /* =============== R_DrawCubemapView =============== */ void R_DrawCubemapView( const vec3_t origin, const vec3_t angles, int size ) { ref_params_t *fd; fd = &RI.refdef; *fd = r_lastRefdef; fd->time = 0; fd->viewport[0] = RI.refdef.viewport[1] = 0; fd->viewport[2] = size; fd->viewport[3] = size; fd->fov_x = 90; fd->fov_y = 90; VectorCopy( origin, fd->vieworg ); VectorCopy( angles, fd->viewangles ); VectorCopy( fd->vieworg, RI.pvsorigin ); R_Set2DMode( false ); // setup scissor RI.scissor[0] = fd->viewport[0]; RI.scissor[1] = glState.height - fd->viewport[3] - fd->viewport[1]; RI.scissor[2] = fd->viewport[2]; RI.scissor[3] = fd->viewport[3]; // setup viewport RI.viewport[0] = fd->viewport[0]; RI.viewport[1] = glState.height - fd->viewport[3] - fd->viewport[1]; RI.viewport[2] = fd->viewport[2]; RI.viewport[3] = fd->viewport[3]; R_RenderScene( fd ); r_oldviewleaf = r_viewleaf = NULL; // force markleafs next frame } static int GL_RenderGetParm( int parm, int arg ) { gltexture_t *glt; switch( parm ) { case PARM_TEX_WIDTH: glt = R_GetTexture( arg ); return glt->width; case PARM_TEX_HEIGHT: glt = R_GetTexture( arg ); return glt->height; case PARM_TEX_SRC_WIDTH: glt = R_GetTexture( arg ); return glt->srcWidth; case PARM_TEX_SRC_HEIGHT: glt = R_GetTexture( arg ); return glt->srcHeight; case PARM_TEX_SKYBOX: ASSERT( arg >= 0 && arg < 6 ); return tr.skyboxTextures[arg]; case PARM_TEX_SKYTEXNUM: return tr.skytexturenum; case PARM_TEX_LIGHTMAP: ASSERT( arg >= 0 && arg < MAX_LIGHTMAPS ); return tr.lightmapTextures[arg]; case PARM_SKY_SPHERE: return world.sky_sphere; case PARM_WORLD_VERSION: return world.version; case PARM_WIDESCREEN: return glState.wideScreen; case PARM_FULLSCREEN: return glState.fullScreen; case PARM_SCREEN_WIDTH: return glState.width; case PARM_SCREEN_HEIGHT: return glState.height; case PARM_MAP_HAS_MIRRORS: return world.has_mirrors; case PARM_CLIENT_INGAME: return CL_IsInGame(); case PARM_MAX_ENTITIES: return clgame.maxEntities; case PARM_TEX_TARGET: glt = R_GetTexture( arg ); return glt->target; case PARM_TEX_TEXNUM: glt = R_GetTexture( arg ); return glt->texnum; case PARM_TEX_FLAGS: glt = R_GetTexture( arg ); return glt->flags; case PARM_FEATURES: return host.features; } return 0; } static void R_GetDetailScaleForTexture( int texture, float *xScale, float *yScale ) { gltexture_t *glt = R_GetTexture( texture ); if( xScale ) *xScale = glt->xscale; if( yScale ) *yScale = glt->yscale; } static void R_GetExtraParmsForTexture( int texture, byte *red, byte *green, byte *blue, byte *density ) { gltexture_t *glt = R_GetTexture( texture ); if( red ) *red = glt->fogParams[0]; if( green ) *green = glt->fogParams[1]; if( blue ) *blue = glt->fogParams[2]; if( density ) *density = glt->fogParams[3]; } /* ================= R_EnvShot ================= */ static void R_EnvShot( const float *vieworg, const char *name, int skyshot ) { static vec3_t viewPoint; if( !name ) { MsgDev( D_ERROR, "R_%sShot: bad name\n", skyshot ? "Sky" : "Env" ); return; } if( cls.scrshot_action != scrshot_inactive ) { if( cls.scrshot_action != scrshot_skyshot && cls.scrshot_action != scrshot_envshot ) MsgDev( D_ERROR, "R_%sShot: subsystem is busy, try later.\n", skyshot ? "Sky" : "Env" ); return; } cls.envshot_vieworg = NULL; // use client view Q_strncpy( cls.shotname, name, sizeof( cls.shotname )); if( vieworg ) { // make sure what viewpoint don't temporare VectorCopy( vieworg, viewPoint ); cls.envshot_vieworg = viewPoint; } // make request for envshot if( skyshot ) cls.scrshot_action = scrshot_skyshot; else cls.scrshot_action = scrshot_envshot; } static void R_SetCurrentEntity( cl_entity_t *ent ) { RI.currententity = ent; // set model also if( RI.currententity != NULL ) { RI.currentmodel = RI.currententity->model; } } static void R_SetCurrentModel( model_t *mod ) { RI.currentmodel = mod; } static lightstyle_t *CL_GetLightStyle( int number ) { ASSERT( number >= 0 && number < MAX_LIGHTSTYLES ); return &cl.lightstyles[number]; } static dlight_t *CL_GetDynamicLight( int number ) { ASSERT( number >= 0 && number < MAX_DLIGHTS ); return &cl_dlights[number]; } static dlight_t *CL_GetEntityLight( int number ) { ASSERT( number >= 0 && number < MAX_ELIGHTS ); return &cl_elights[number]; } static void CL_GetBeamChains( BEAM ***active_beams, BEAM ***free_beams, particle_t ***free_trails ) { *active_beams = &cl_active_beams; *free_beams = &cl_free_beams; *free_trails = &cl_free_trails; } static void GL_SetWorldviewProjectionMatrix( const float *glmatrix ) { if( !glmatrix ) return; Matrix4x4_FromArrayFloatGL( RI.worldviewProjectionMatrix, glmatrix ); } static const char *GL_TextureName( unsigned int texnum ) { return R_GetTexture( texnum )->name; } static int GL_LoadTextureNoFilter( const char *name, const byte *buf, size_t size, int flags ) { return GL_LoadTexture( name, buf, size, flags, NULL ); } static render_api_t gRenderAPI = { GL_RenderGetParm, R_GetDetailScaleForTexture, R_GetExtraParmsForTexture, CL_GetLightStyle, CL_GetDynamicLight, CL_GetEntityLight, TextureToTexGamma, CL_GetBeamChains, R_SetCurrentEntity, R_SetCurrentModel, GL_SetWorldviewProjectionMatrix, R_StoreEfrags, GL_FindTexture, GL_TextureName, GL_LoadTextureNoFilter, GL_CreateTexture, GL_FreeTexture, DrawSingleDecal, CL_DrawParticlesExternal, R_EnvShot, COM_CompareFileTime, Host_Error, pfnSPR_LoadExt, AVI_LoadVideo, AVI_GetVideoInfo, AVI_GetAudioInfo, AVI_GetAudioChunk, AVI_GetVideoFrameNumber, AVI_GetVideoFrame, R_UploadStretchRaw, AVI_FreeVideo, AVI_IsActive, GL_Bind, GL_SelectTexture, GL_LoadTexMatrixExt, GL_LoadIdentityTexMatrix, GL_CleanUpTextureUnits, GL_TexGen, R_EntityRemoveDecals, R_DecalSetupVerts, }; /* =============== R_InitRenderAPI Initialize client external rendering =============== */ qboolean R_InitRenderAPI( void ) { if( clgame.dllFuncs.pfnGetRenderInterface ) { if( clgame.dllFuncs.pfnGetRenderInterface( CL_RENDER_INTERFACE_VERSION, &gRenderAPI, &clgame.drawFuncs )) { MsgDev( D_AICONSOLE, "CL_LoadProgs: ^2initailized extended RenderAPI ^7ver. %i\n", CL_RENDER_INTERFACE_VERSION ); return true; } // make sure what render functions is cleared Q_memset( &clgame.drawFuncs, 0, sizeof( clgame.drawFuncs )); return false; // just tell user about problems } // render interface is missed return true; }