/* Copyright (C) 1997-2001 Id Software, Inc. Copyright (C) 2002-2007 Victor Luchits 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 2 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // r_surf.c: surface-related refresh code #include "r_local.h" #include "mathlib.h" #include "matrix_lib.h" static vec3_t modelorg; // relative to viewpoint static vec3_t modelmins; static vec3_t modelmaxs; /* ============================================================= BRUSH MODELS ============================================================= */ /* ================= R_SurfPotentiallyVisible ================= */ bool R_SurfPotentiallyVisible( msurface_t *surf ) { if( surf->facetype == MST_FLARE ) return true; if( surf->flags & SURF_NODRAW ) return false; if( !surf->mesh || R_InvalidMesh( surf->mesh ) ) return false; return true; } /* ================= R_CullSurface ================= */ bool R_CullSurface( msurface_t *surf, uint clipflags ) { ref_shader_t *shader = surf->shader; if(( shader->flags & SHADER_SKY ) && r_fastsky->integer ) return true; if( r_nocull->integer ) return false; if( shader->flags & SHADER_AUTOSPRITE ) return false; // never cull turblent or warp surfaces if( shader->tessSize ) return false; // flare if( surf->facetype == MST_FLARE ) { if( r_flares->integer && r_flarefade->value ) { vec3_t origin; if( RI.currentmodel != r_worldmodel ) { Matrix3x3_Transform( RI.currententity->axis, surf->origin, origin ); VectorAdd( origin, RI.currententity->origin, origin ); } else { VectorCopy( surf->origin, origin ); } // cull it because we don't want to sort unneeded things if( ( origin[0] - RI.viewOrigin[0] ) * RI.vpn[0] + ( origin[1] - RI.viewOrigin[1] ) * RI.vpn[1] + ( origin[2] - RI.viewOrigin[2] ) * RI.vpn[2] < 0 ) return true; return ( clipflags && R_CullSphere( origin, 1, clipflags ) ); } return true; } if( surf->facetype == MST_PLANAR && r_faceplanecull->integer && !RI.currententity->outlineHeight && ( shader->flags & (SHADER_CULL_FRONT|SHADER_CULL_BACK))) { // Vic: I hate q3map2. I really do. if( !VectorCompare( surf->plane->normal, vec3_origin )) { float dist; dist = PlaneDiff( modelorg, surf->plane ); if( ( shader->flags & SHADER_CULL_FRONT ) || ( RI.params & RP_MIRRORVIEW )) { if( dist <= BACKFACE_EPSILON ) return true; } else { if( dist >= -BACKFACE_EPSILON ) return true; } } } return ( clipflags && R_CullBox( surf->mins, surf->maxs, clipflags )); } /* ================= R_AddSurfaceToList ================= */ static meshbuffer_t *R_AddSurfaceToList( msurface_t *surf, unsigned int clipflags ) { ref_shader_t *shader; meshbuffer_t *mb; if( R_CullSurface( surf, clipflags ) ) return NULL; shader = ((r_drawworld->integer == 2) ? R_OcclusionShader() : surf->shader); if( shader->flags & SHADER_SKYPARMS ) { bool vis = R_AddSkySurface( surf ); if(( RI.params & RP_NOSKY ) && vis ) { R_AddMeshToList( MB_MODEL, surf->fog, shader, surf - r_worldbrushmodel->surfaces + 1 ); RI.params &= ~RP_NOSKY; } return NULL; } if( OCCLUSION_QUERIES_ENABLED( RI ) ) { if( shader->flags & SHADER_PORTAL ) R_SurfOcclusionQueryKey( RI.currententity, surf ); if( OCCLUSION_OPAQUE_SHADER( shader ) ) R_AddOccludingSurface( surf, shader ); } c_brush_polys++; mb = R_AddMeshToList( surf->facetype == MST_FLARE ? MB_SPRITE : MB_MODEL, surf->fog, shader, surf - r_worldbrushmodel->surfaces + 1 ); RI.surfmbuffers[surf - r_worldbrushmodel->surfaces] = mb; return mb; } /* ================= R_CullBrushModel ================= */ bool R_CullBrushModel( ref_entity_t *e ) { int i; bool rotated; ref_model_t *model = e->model; mbrushmodel_t *bmodel = ( mbrushmodel_t * )model->extradata; if( bmodel->nummodelsurfaces == 0 ) return true; if( !Matrix3x3_Compare( e->axis, matrix3x3_identity )) { rotated = true; for( i = 0; i < 3; i++ ) { modelmins[i] = e->origin[i] - model->radius * e->scale; modelmaxs[i] = e->origin[i] + model->radius * e->scale; } if( R_CullSphere( e->origin, model->radius * e->scale, RI.clipFlags ) ) return true; } else { rotated = false; VectorMA( e->origin, e->scale, model->mins, modelmins ); VectorMA( e->origin, e->scale, model->maxs, modelmaxs ); if( R_CullBox( modelmins, modelmaxs, RI.clipFlags ) ) return true; } if( RI.refdef.flags & ( RDF_PORTALINVIEW|RDF_SKYPORTALINVIEW ) || ( RI.params & RP_SKYPORTALVIEW )) { if( rotated ) { if( R_VisCullSphere( e->origin, model->radius * e->scale ) ) return true; } else { if( R_VisCullBox( modelmins, modelmaxs ) ) return true; } } return false; } /* ================= R_AddBrushModelToList ================= */ void R_AddBrushModelToList( ref_entity_t *e ) { uint i; bool rotated; ref_model_t *model = e->model; mbrushmodel_t *bmodel = ( mbrushmodel_t * )model->extradata; msurface_t *psurf; uint dlightbits; meshbuffer_t *mb; e->outlineHeight = r_worldent->outlineHeight; Vector4Copy( r_worldent->outlineColor, e->outlineColor ); rotated = !Matrix3x3_Compare( e->axis, matrix3x3_identity ); VectorSubtract( RI.refdef.vieworg, e->origin, modelorg ); if( rotated ) { vec3_t temp; VectorCopy( modelorg, temp ); Matrix3x3_Transform( e->axis, temp, modelorg ); } dlightbits = 0; if(( r_dynamiclight->integer == 1 ) && !r_fullbright->integer && !( RI.params & RP_SHADOWMAPVIEW )) { for( i = 0; i < r_numDlights; i++ ) { if( BoundsIntersect( modelmins, modelmaxs, r_dlights[i].mins, r_dlights[i].maxs )) dlightbits |= ( 1<firstmodelsurface; i < (unsigned)bmodel->nummodelsurfaces; i++, psurf++ ) { if( !R_SurfPotentiallyVisible( psurf ) ) continue; if( RI.params & RP_SHADOWMAPVIEW ) { if( psurf->visframe != r_framecount ) continue; if( ( psurf->shader->sort >= SORT_OPAQUE ) && ( psurf->shader->sort <= SORT_BANNER ) ) { if( prevRI.surfmbuffers[psurf - r_worldbrushmodel->surfaces] ) { if( !R_CullSurface( psurf, 0 ) ) { RI.params |= RP_WORLDSURFVISIBLE; prevRI.surfmbuffers[psurf - r_worldbrushmodel->surfaces]->shadowbits |= RI.shadowGroup->bit; } } } continue; } psurf->visframe = r_framecount; mb = R_AddSurfaceToList( psurf, 0 ); if( mb ) { mb->sortkey |= ( ( psurf->superLightStyle+1 ) << 10 ); if( R_SurfPotentiallyLit( psurf ) ) mb->dlightbits = dlightbits; } } } /* ============================================================= WORLD MODEL ============================================================= */ /* ================ R_MarkLeafSurfaces ================ */ static void R_MarkLeafSurfaces( msurface_t **mark, uint clipflags, uint dlightbits ) { unsigned int newDlightbits; msurface_t *surf; meshbuffer_t *mb; do { surf = *mark++; // note that R_AddSurfaceToList may set meshBuffer to NULL // for world ALL surfaces to prevent referencing to freed memory region if( surf->visframe != r_framecount ) { surf->visframe = r_framecount; mb = R_AddSurfaceToList( surf, clipflags ); if( mb ) mb->sortkey |= ( ( surf->superLightStyle+1 ) << 10 ); } else { mb = RI.surfmbuffers[surf - r_worldbrushmodel->surfaces]; } newDlightbits = mb ? dlightbits & ~mb->dlightbits : 0; if( newDlightbits && R_SurfPotentiallyLit( surf ) ) mb->dlightbits |= R_AddSurfDlighbits( surf, newDlightbits ); } while( *mark ); } /* ================ R_RecursiveWorldNode ================ */ static void R_RecursiveWorldNode( mnode_t *node, uint clipflags, uint dlightbits ) { uint i, newDlightbits; const cplane_t *clipplane; int clipped; mleaf_t *pleaf; while( 1 ) { if( node->pvsframe != r_pvsframecount ) return; if( clipflags ) { for( i = 0, clipplane = RI.frustum; i < 6; i++, clipplane++ ) { if(!(clipflags & (1<mins, node->maxs, clipplane ); if( clipped == 2 ) return; if( clipped == 1 ) clipflags &= ~(1<plane ) break; newDlightbits = 0; if( dlightbits ) { float dist; for( i = 0; i < r_numDlights; i++ ) { if( !( dlightbits & (1<plane ); if( dist < -r_dlights[i].intensity ) dlightbits &= ~(1<children[0], clipflags, dlightbits ); node = node->children[1]; dlightbits = newDlightbits; } // if a leaf node, draw stuff pleaf = ( mleaf_t * )node; pleaf->visframe = r_framecount; // add leaf bounds to view bounds for( i = 0; i < 3; i++ ) { RI.visMins[i] = min( RI.visMins[i], pleaf->mins[i] ); RI.visMaxs[i] = max( RI.visMaxs[i], pleaf->maxs[i] ); } R_MarkLeafSurfaces( pleaf->firstVisSurface, clipflags, dlightbits ); c_world_leafs++; } /* ================ R_MarkShadowLeafSurfaces ================ */ static void R_MarkShadowLeafSurfaces( msurface_t **mark, unsigned int clipflags ) { msurface_t *surf; meshbuffer_t *mb; const unsigned int bit = RI.shadowGroup->bit; do { surf = *mark++; if( surf->flags & ( SURF_NOIMPACT|SURF_NODRAW ) ) continue; mb = prevRI.surfmbuffers[surf - r_worldbrushmodel->surfaces]; if( !mb || (mb->shadowbits & bit) ) continue; // this surface is visible in previous RI, not marked as shadowed... if( ( surf->shader->sort >= SORT_OPAQUE ) && ( surf->shader->sort <= SORT_ALPHATEST ) ) { // ...is opaque if( !R_CullSurface( surf, clipflags ) ) { // and is visible to the light source too RI.params |= RP_WORLDSURFVISIBLE; mb->shadowbits |= bit; } } } while( *mark ); } /* ================ R_LinearShadowLeafs ================ */ static void R_LinearShadowLeafs( void ) { uint i, j; uint cpf; const cplane_t *clipplane; mleaf_t *pleaf; for( j = r_worldbrushmodel->numleafs, pleaf = r_worldbrushmodel->leafs; j > 0; j--, pleaf++ ) { if( pleaf->visframe != r_framecount ) continue; if( !( RI.shadowGroup->vis[pleaf->cluster>>3] & ( 1<<( pleaf->cluster&7 ) ) ) ) continue; cpf = RI.clipFlags; for( i = 0, clipplane = RI.frustum; i < 6; i++, clipplane++ ) { int clipped = BoxOnPlaneSide( pleaf->mins, pleaf->maxs, clipplane ); if( clipped == 2 ) break; if( clipped == 1 ) cpf &= ~(1<firstVisSurface, cpf ); c_world_leafs++; } } } //================================================================================== int r_surfQueryKeys[MAX_SURF_QUERIES]; /* =============== R_ClearSurfOcclusionQueryKeys =============== */ void R_ClearSurfOcclusionQueryKeys( void ) { memset( r_surfQueryKeys, -1, sizeof( r_surfQueryKeys ) ); } /* =============== R_SurfOcclusionQueryKey =============== */ int R_SurfOcclusionQueryKey( ref_entity_t *e, msurface_t *surf ) { int i; int *keys = r_surfQueryKeys; int key = surf - r_worldbrushmodel->surfaces; if( e != r_worldent ) return -1; for( i = 0; i < MAX_SURF_QUERIES; i++ ) { if( keys[i] >= 0 ) { if( keys[i] == key ) return i; } else { keys[i] = key; return i; } } return -1; } /* =============== R_SurfIssueOcclusionQueries =============== */ void R_SurfIssueOcclusionQueries( void ) { int i, *keys = r_surfQueryKeys; msurface_t *surf; for( i = 0; keys[i] >= 0; i++ ) { surf = &r_worldbrushmodel->surfaces[keys[i]]; R_IssueOcclusionQuery( R_GetOcclusionQueryNum( OQ_CUSTOM, i ), r_worldent, surf->mins, surf->maxs ); } } //================================================================================== /* ============= R_CalcDistancesToFogVolumes ============= */ static void R_CalcDistancesToFogVolumes( void ) { int i; mfog_t *fog; for( i = 0, fog = r_worldbrushmodel->fogs; i < r_worldbrushmodel->numfogs; i++, fog++ ) RI.fog_dist_to_eye[fog - r_worldbrushmodel->fogs] = PlaneDiff( RI.viewOrigin, fog->visibleplane ); } /* ============= R_DrawWorld ============= */ void R_DrawWorld( void ) { int clipflags, msec = 0; unsigned int dlightbits; if( !r_drawworld->integer ) return; if( !r_worldmodel ) return; if( RI.refdef.flags & RDF_NOWORLDMODEL ) return; VectorCopy( RI.refdef.vieworg, modelorg ); RI.previousentity = NULL; RI.currententity = r_worldent; RI.currentmodel = RI.currententity->model; if( r_outlines_world->integer && r_viewcluster != -1 ) RI.currententity->outlineHeight = max( 0.0f, r_outlines_world->value ); else RI.currententity->outlineHeight = 0.0f; Vector4Copy( mapConfig.outlineColor, RI.currententity->outlineColor ); if( !( RI.params & RP_SHADOWMAPVIEW ) ) { R_AllocMeshbufPointers( &RI ); Mem_Set( RI.surfmbuffers, 0, r_worldbrushmodel->numsurfaces * sizeof( meshbuffer_t * )); R_CalcDistancesToFogVolumes(); } ClearBounds( RI.visMins, RI.visMaxs ); R_ClearSkyBox(); if( r_nocull->integer ) clipflags = 0; else clipflags = RI.clipFlags; if( r_dynamiclight->integer != 1 || r_fullbright->integer ) dlightbits = 0; else dlightbits = r_numDlights < 32 ? ( 1 << r_numDlights ) - 1 : -1; if( r_speeds->integer ) msec = Sys_Milliseconds(); if( RI.params & RP_SHADOWMAPVIEW ) R_LinearShadowLeafs (); else R_RecursiveWorldNode( r_worldbrushmodel->nodes, clipflags, dlightbits ); if( r_speeds->integer ) r_world_node += Sys_Milliseconds() - msec; } /* =============== R_MarkLeaves Mark the leaves and nodes that are in the PVS for the current cluster =============== */ void R_MarkLeaves( void ) { byte *vis; int i; mleaf_t *leaf, **pleaf; mnode_t *node; int cluster; if( RI.refdef.flags & RDF_NOWORLDMODEL ) return; if( r_oldviewcluster == r_viewcluster && ( RI.refdef.flags & RDF_OLDAREABITS ) && !r_novis->integer && r_viewcluster != -1 ) return; if( RI.params & RP_SHADOWMAPVIEW ) return; // development aid to let you run around and see exactly where // the pvs ends if( r_lockpvs->integer ) return; r_pvsframecount++; r_oldviewcluster = r_viewcluster; if( r_novis->integer || r_viewcluster == -1 || !r_worldbrushmodel->vis ) { // mark everything for( pleaf = r_worldbrushmodel->visleafs, leaf = *pleaf; leaf; leaf = *pleaf++ ) leaf->pvsframe = r_pvsframecount; for( i = 0, node = r_worldbrushmodel->nodes; i < r_worldbrushmodel->numnodes; i++, node++ ) node->pvsframe = r_pvsframecount; return; } vis = Mod_ClusterPVS( r_viewcluster, r_worldmodel ); for( pleaf = r_worldbrushmodel->visleafs, leaf = *pleaf; leaf; leaf = *pleaf++ ) { cluster = leaf->cluster; // check for door connected areas if( RI.refdef.areabits ) { if( !( RI.refdef.areabits[leaf->area>>3] & ( 1<<( leaf->area&7 ) ) ) ) continue; // not visible } if( vis[cluster>>3] & ( 1<<( cluster&7 ) ) ) { node = (mnode_t *)leaf; do { if( node->pvsframe == r_pvsframecount ) break; node->pvsframe = r_pvsframecount; node = node->parent; } while( node ); } } }