/* =========================================================================== 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" // returns true if the grid is completely culled away. // also sets the clipped hint bit in tess static qbool R_CullTriSurf( const srfTriangles_t* cv ) { return ( R_CullLocalBox( cv->bounds ) == CULL_OUT ); } // returns true if the grid is completely culled away. // also sets the clipped hint bit in tess static qbool R_CullGrid( const srfGridMesh_t* cv ) { int sphereCull; if ( r_nocurves->integer ) { return qtrue; } if ( tr.currentEntityNum != ENTITYNUM_WORLD ) { sphereCull = R_CullLocalPointAndRadius( cv->localOrigin, cv->meshRadius ); } else { sphereCull = R_CullPointAndRadius( cv->localOrigin, cv->meshRadius ); } // check for trivial reject if ( sphereCull == CULL_OUT ) { tr.pc[RF_BEZ_CULL_S_OUT]++; return qtrue; } // check bounding box if necessary if ( sphereCull == CULL_CLIP ) { tr.pc[RF_BEZ_CULL_S_CLIP]++; int boxCull = R_CullLocalBox( cv->meshBounds ); if ( boxCull == CULL_OUT ) { tr.pc[RF_BEZ_CULL_B_OUT]++; return qtrue; } else if ( boxCull == CULL_IN ) { tr.pc[RF_BEZ_CULL_B_IN]++; } else { tr.pc[RF_BEZ_CULL_B_CLIP]++; } } else { tr.pc[RF_BEZ_CULL_S_IN]++; } return qfalse; } // try to cull surfaces before they are added to the draw list // this code will also allow mirrors on both sides of a model without recursion static qbool R_CullSurface( const surfaceType_t* surface, const shader_t* shader ) { if ( r_nocull->integer ) { return qfalse; } if ( *surface == SF_GRID ) { return R_CullGrid( (const srfGridMesh_t*)surface ); } if ( *surface == SF_TRIANGLES ) { return R_CullTriSurf( (const srfTriangles_t*)surface ); } if ( *surface != SF_FACE ) { return qfalse; } if ( shader->cullType == CT_TWO_SIDED ) { return qfalse; } const srfSurfaceFace_t* face = (const srfSurfaceFace_t*)surface; float d = DotProduct( tr.or.viewOrigin, face->plane.normal ); // don't cull exactly on the plane, because there are levels of rounding // through the BSP, ICD, and hardware that may cause pixel gaps if an // epsilon isn't allowed here if ( shader->cullType == CT_FRONT_SIDED ) { if ( d < face->plane.dist - 8 ) { return qtrue; } } else { if ( d > face->plane.dist + 8 ) { return qtrue; } } return qfalse; } /////////////////////////////////////////////////////////////// static qbool R_LightCullBounds( const dlight_t* dl, const vec3_t mins, const vec3_t maxs ) { if (dl->transformed[0] - dl->radius > maxs[0]) return qtrue; if (dl->transformed[0] + dl->radius < mins[0]) return qtrue; if (dl->transformed[1] - dl->radius > maxs[1]) return qtrue; if (dl->transformed[1] + dl->radius < mins[1]) return qtrue; if (dl->transformed[2] - dl->radius > maxs[2]) return qtrue; if (dl->transformed[2] + dl->radius < mins[2]) return qtrue; return qfalse; } static qbool R_LightCullFace( const srfSurfaceFace_t* face, const dlight_t* dl ) { float d = DotProduct( dl->origin, face->plane.normal ) - face->plane.dist; if ( (d < -dl->radius) || (d > dl->radius) ) return qtrue; return qfalse; } static qbool R_LightCullSurface( const surfaceType_t* surface, const dlight_t* dl ) { switch (*surface) { case SF_FACE: return R_LightCullFace( (const srfSurfaceFace_t*)surface, dl ); case SF_GRID: { const srfGridMesh_t* grid = (const srfGridMesh_t*)surface; return R_LightCullBounds( dl, grid->meshBounds[0], grid->meshBounds[1] ); } case SF_TRIANGLES: { const srfTriangles_t* tris = (const srfTriangles_t*)surface; return R_LightCullBounds( dl, tris->bounds[0], tris->bounds[1] ); } } return qfalse; } static void R_AddWorldSurface( msurface_t* surf ) { if ( surf->vcBSP == tr.viewCount ) return; // already checked during this BSP walk surf->vcBSP = tr.viewCount; if ( R_CullSurface( surf->data, surf->shader ) ) return; surf->vcVisible = tr.viewCount; R_AddDrawSurf( surf->data, surf->shader, surf->fogIndex ); } /* ============================================================= WORLD MODEL ============================================================= */ static void R_RecursiveWorldNode( mnode_t *node, int planeBits ) { do { // if the node wasn't marked as potentially visible, exit if (node->visframe != tr.visCount) return; // if the bounding volume is completely outside the frustum, dump it if ( !r_nocull->integer ) { int r; if ( planeBits & 1 ) { r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[0]); if (r == 2) { return; // culled } if ( r == 1 ) { planeBits &= ~1; // all descendants will also be in front } } if ( planeBits & 2 ) { r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[1]); if (r == 2) { return; // culled } if ( r == 1 ) { planeBits &= ~2; // all descendants will also be in front } } if ( planeBits & 4 ) { r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[2]); if (r == 2) { return; // culled } if ( r == 1 ) { planeBits &= ~4; // all descendants will also be in front } } if ( planeBits & 8 ) { r = BoxOnPlaneSide(node->mins, node->maxs, &tr.viewParms.frustum[3]); if (r == 2) { return; // culled } if ( r == 1 ) { planeBits &= ~8; // all descendants will also be in front } } } if (node->contents != CONTENTS_NODE) break; // recurse down the children, front side first R_RecursiveWorldNode( node->children[0], planeBits ); // tail recurse node = node->children[1]; } while ( 1 ); // leaf node, so add mark surfaces tr.pc[RF_LEAFS]++; // add to z buffer bounds if ( node->mins[0] < tr.viewParms.visBounds[0][0] ) { tr.viewParms.visBounds[0][0] = node->mins[0]; } if ( node->mins[1] < tr.viewParms.visBounds[0][1] ) { tr.viewParms.visBounds[0][1] = node->mins[1]; } if ( node->mins[2] < tr.viewParms.visBounds[0][2] ) { tr.viewParms.visBounds[0][2] = node->mins[2]; } if ( node->maxs[0] > tr.viewParms.visBounds[1][0] ) { tr.viewParms.visBounds[1][0] = node->maxs[0]; } if ( node->maxs[1] > tr.viewParms.visBounds[1][1] ) { tr.viewParms.visBounds[1][1] = node->maxs[1]; } if ( node->maxs[2] > tr.viewParms.visBounds[1][2] ) { tr.viewParms.visBounds[1][2] = node->maxs[2]; } // add the individual surfaces int c = node->nummarksurfaces; msurface_t** mark = node->firstmarksurface; while (c--) { // the surface may have already been added if it spans multiple leafs msurface_t* surf = *mark; R_AddWorldSurface( surf ); mark++; } } /////////////////////////////////////////////////////////////// static void R_AddLitSurface( msurface_t* surf, const dlight_t* light ) { // since we're not worried about offscreen lights casting into the frustum (ATM !!!) // only add the "lit" version of this surface if it was already added to the view //if ( surf->viewCount != tr.viewCount ) // return; // surfaces that were faceculled will still have the current viewCount in vcBSP // because that's set to indicate that it's BEEN vis tested at all, to avoid // repeated vis tests, not whether it actually PASSED the vis test or not // only light surfaces that are GENUINELY visible, as opposed to merely in a visible LEAF if ( surf->vcVisible != tr.viewCount ) return; if ( surf->shader->surfaceFlags & (SURF_NODLIGHT | SURF_SKY) ) return; if ( surf->shader->sort != SS_OPAQUE ) return; if ( surf->lightCount == tr.lightCount ) return; // already in the lit list (or already culled) for this light surf->lightCount = tr.lightCount; if ( R_LightCullSurface( surf->data, light ) ) { tr.pc[RF_LIT_CULLS]++; return; } R_AddLitSurf( surf->data, surf->shader, surf->fogIndex ); } static void R_RecursiveLightNode( mnode_t* node ) { do { // if the node wasn't marked as potentially visible, exit if (node->visframe != tr.visCount) return; if (node->contents != CONTENTS_NODE) break; qbool children[2]; children[0] = children[1] = qfalse; float d = DotProduct( tr.light->origin, node->plane->normal ) - node->plane->dist; if ( d > -tr.light->radius ) { children[0] = qtrue; } if ( d < tr.light->radius ) { children[1] = qtrue; } if ( children[0] && children[1] ) { R_RecursiveLightNode( node->children[0] ); node = node->children[1]; } else if ( children[0] ) { node = node->children[0]; } else if ( children[1] ) { node = node->children[1]; } else { return; } } while ( 1 ); tr.pc[RF_LIT_LEAFS]++; // add the individual surfaces int c = node->nummarksurfaces; msurface_t** mark = node->firstmarksurface; while (c--) { // the surface may have already been added if it spans multiple leafs msurface_t* surf = *mark; R_AddLitSurface( surf, tr.light ); mark++; } } /////////////////////////////////////////////////////////////// // BRUSH MODELS void R_AddBrushModelSurfaces( const trRefEntity_t* re ) { const model_t* model = R_GetModelByHandle( re->e.hModel ); const bmodel_t* bmodel = model->bmodel; if ( R_CullLocalBox( bmodel->bounds ) == CULL_OUT ) return; for ( int s = 0; s < bmodel->numSurfaces; ++s ) { R_AddWorldSurface( bmodel->firstSurface + s ); } R_TransformDlights( tr.refdef.num_dlights, tr.refdef.dlights, &tr.or ); for ( int i = 0; i < tr.refdef.num_dlights; ++i ) { dlight_t* dl = &tr.refdef.dlights[i]; if (R_LightCullBounds( dl, bmodel->bounds[0], bmodel->bounds[1] )) continue; ++tr.lightCount; tr.light = dl; for ( int s = 0; s < bmodel->numSurfaces; ++s ) { R_AddLitSurface( bmodel->firstSurface + s, dl ); } } } /////////////////////////////////////////////////////////////// static mnode_t* R_PointInLeaf( const vec3_t p ) { if ( !tr.world ) { ri.Error( ERR_DROP, "R_PointInLeaf: no world" ); } mnode_t* node = tr.world->nodes; while (node->contents == CONTENTS_NODE) { const cplane_t* plane = node->plane; float d = DotProduct (p,plane->normal) - plane->dist; if (d > 0) { node = node->children[0]; } else { node = node->children[1]; } } return node; } static const byte* R_ClusterPVS( int cluster ) { if ( !tr.world || !tr.world->vis || cluster < 0 || cluster >= tr.world->numClusters ) return tr.world->novis; return tr.world->vis + cluster * tr.world->clusterBytes; } qbool R_inPVS( const vec3_t p1, const vec3_t p2 ) { const mnode_t* leaf; leaf = R_PointInLeaf( p1 ); const byte* vis = CM_ClusterPVS( leaf->cluster ); leaf = R_PointInLeaf( p2 ); if ( !(vis[leaf->cluster>>3] & (1<<(leaf->cluster&7))) ) { return qfalse; } return qtrue; } // mark the leaves and nodes that are in the PVS for the current cluster static void R_MarkLeaves() { mnode_t* leaf; int i, cluster; // lockpvs lets designers walk around to determine the // extent of the current pvs if ( r_lockpvs->integer ) { return; } // current viewcluster leaf = R_PointInLeaf( tr.viewParms.pvsOrigin ); cluster = leaf->cluster; tr.pc[RF_LEAF_CLUSTER] = cluster; tr.pc[RF_LEAF_AREA] = leaf->area; // if the cluster is the same and the area visibility matrix // hasn't changed, we don't need to mark everything again if ( tr.viewCluster == cluster && !tr.refdef.areamaskModified ) { return; } tr.visCount++; tr.viewCluster = cluster; if ( r_novis->integer || tr.viewCluster == -1 ) { for ( i = 0; i < tr.world->numnodes; ++i ) { if (tr.world->nodes[i].contents != CONTENTS_SOLID) { tr.world->nodes[i].visframe = tr.visCount; } } return; } const byte* vis = R_ClusterPVS( tr.viewCluster ); for ( i = 0, leaf = tr.world->nodes; i < tr.world->numnodes; ++i, ++leaf ) { cluster = leaf->cluster; if ( cluster < 0 || cluster >= tr.world->numClusters ) { continue; } // check general pvs if ( !(vis[cluster>>3] & (1<<(cluster&7))) ) { continue; } // check for door connection if ( (tr.refdef.areamask[leaf->area>>3] & (1<<(leaf->area&7)) ) ) { continue; // not visible } mnode_t* parent = leaf; do { if (parent->visframe == tr.visCount) break; parent->visframe = tr.visCount; parent = parent->parent; } while (parent); } } void R_AddWorldSurfaces() { if ( !r_drawworld->integer ) { return; } if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) { return; } tr.currentEntityNum = ENTITYNUM_WORLD; tr.shiftedEntityNum = tr.currentEntityNum << QSORT_ENTITYNUM_SHIFT; // determine which leaves are in the PVS / areamask R_MarkLeaves(); // add all the visible surfaces and regenerate the visible min/max ClearBounds( tr.viewParms.visBounds[0], tr.viewParms.visBounds[1] ); R_RecursiveWorldNode( tr.world->nodes, 15 ); if ( tr.refdef.num_dlights > MAX_DLIGHTS ) tr.refdef.num_dlights = MAX_DLIGHTS; // "transform" all the dlights so that dl->transformed is actually populated // (even though HERE it's == dl->origin) so we can always use R_LightCullBounds // instead of having copypasted versions for both world and local cases R_TransformDlights( tr.refdef.num_dlights, tr.refdef.dlights, &tr.viewParms.world ); for ( int i = 0; i < tr.refdef.num_dlights; ++i ) { dlight_t* dl = &tr.refdef.dlights[i]; dl->head = dl->tail = 0; if ( R_CullPointAndRadius( dl->origin, dl->radius ) == CULL_OUT ) { tr.pc[RF_LIGHT_CULL_OUT]++; continue; } tr.pc[RF_LIGHT_CULL_IN]++; ++tr.lightCount; tr.light = dl; R_RecursiveLightNode( tr.world->nodes ); } }