cnq3/code/renderer/tr_world.cpp

588 lines
15 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"
// 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.orient.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.orient );
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 );
}
}