/* Copyright (C) 2003-2006 Andrey Nazarov 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. */ #include "gl_local.h" vec3_t modelViewOrigin; /* viewer origin in model space */ static vec3_t lightcolor; static qboolean GL_LightPoint_r( bspNode_t *node, vec3_t start, vec3_t end ) { cplane_t *plane; vec_t startFrac, endFrac, midFrac; vec3_t mid; int side; qboolean ret; bspSurface_t *surf; bspTexinfo_t *texinfo; int i, pitch; int s, t; byte *b1, *b2, *b3, *b4; int fracu, fracv; int w1, w2, w3, w4; int color[3]; if( !( plane = node->plane ) ) { return qfalse; } /* calculate distancies */ startFrac = DotProduct( plane->normal, start ) - plane->dist; endFrac = DotProduct( plane->normal, end ) - plane->dist; side = ( startFrac < 0 ); if( ( endFrac < 0 ) == side ) { /* both points are one the same side */ return GL_LightPoint_r( node->children[side], start, end ); } /* find crossing point */ midFrac = startFrac / ( startFrac - endFrac ); mid[0] = start[0] + ( end[0] - start[0] ) * midFrac; mid[1] = start[1] + ( end[1] - start[1] ) * midFrac; mid[2] = start[2] + ( end[2] - start[2] ) * midFrac; /* check near side */ ret = GL_LightPoint_r( node->children[side], start, mid ); if( ret ) { return ret; } surf = node->firstFace; for( i = 0; i < node->numFaces; i++, surf++ ) { texinfo = surf->texinfo; if( texinfo->flags & (SURF_WARP|SURF_SKY) ) { continue; } if( !surf->lightmap ) { continue; } s = DotProduct( texinfo->axis[0], mid ) + texinfo->offset[0]; t = DotProduct( texinfo->axis[1], mid ) + texinfo->offset[1]; s -= surf->texturemins[0]; t -= surf->texturemins[1]; if( s < 0 || t < 0 ) { continue; } if( s > surf->extents[0] || t > surf->extents[1] ) { continue; } fracu = s & 15; fracv = t & 15; s >>= 4; t >>= 4; pitch = ( surf->extents[0] >> 4 ) + 1; b1 = &surf->lightmap[3 * ( ( t + 0 ) * pitch + ( s + 0 ) )]; b2 = &surf->lightmap[3 * ( ( t + 0 ) * pitch + ( s + 1 ) )]; b3 = &surf->lightmap[3 * ( ( t + 1 ) * pitch + ( s + 1 ) )]; b4 = &surf->lightmap[3 * ( ( t + 1 ) * pitch + ( s + 0 ) )]; w1 = ( 16 - fracu ) * ( 16 - fracv ); w2 = fracu * ( 16 - fracv ); w3 = fracu * fracv; w4 = ( 16 - fracu ) * fracv; color[0] = ( w1 * b1[0] + w2 * b2[0] + w3 * b3[0] + w4 * b4[0] ) >> 8; color[1] = ( w1 * b1[1] + w2 * b2[1] + w3 * b3[1] + w4 * b4[1] ) >> 8; color[2] = ( w1 * b1[2] + w2 * b2[2] + w3 * b3[2] + w4 * b4[2] ) >> 8; VectorMA( lightcolor, 1.0f / 255, color, lightcolor ); return qtrue; } /* check far side */ return GL_LightPoint_r( node->children[side^1], mid, end ); } void GL_LightPoint( vec3_t origin, vec3_t dest ) { extern cvar_t *gl_modulate_hack; vec3_t point; dlight_t *light; vec3_t dir; vec_t dist, f; int i; if( !r_world.name[0] || gl_fullbright->integer ) { VectorSet( dest, 1, 1, 1 ); return; } point[0] = origin[0]; point[1] = origin[1]; point[2] = origin[2] - 8192; VectorClear( lightcolor ); if( !r_world.lightmap || !GL_LightPoint_r( r_world.nodes, origin, point ) ) { VectorSet( lightcolor, 1, 1, 1 ); } if( gl_modulate_hack && gl_modulate_hack->integer ) { VectorScale( lightcolor, gl_modulate->value, lightcolor ); } for( i = 0, light = glr.fd.dlights; i < glr.fd.num_dlights; i++, light++ ) { VectorSubtract( light->origin, origin, dir ); dist = VectorLength( dir ); if( dist > light->intensity ) { continue; } f = 1.0f - dist / light->intensity; VectorMA( lightcolor, f, light->color, lightcolor ); } /* apply modulate twice to mimic original ref_gl behavior */ VectorScale( lightcolor, gl_modulate->value, lightcolor ); for( i = 0; i < 3; i++ ) { if( lightcolor[i] > 1 ) { lightcolor[i] = 1; } else if( lightcolor[i] < 0 ) { lightcolor[i] = 0; } } VectorCopy( lightcolor, dest ); } static void GL_MarkLights_r( bspNode_t *node, dlight_t *light ) { cplane_t *plane; vec_t dot; int lightbit, count; bspSurface_t *face; if( !( plane = node->plane ) ) { return; } switch( plane->type ) { case PLANE_X: dot = light->transformed[0] - plane->dist; break; case PLANE_Y: dot = light->transformed[1] - plane->dist; break; case PLANE_Z: dot = light->transformed[2] - plane->dist; break; default: dot = DotProduct( light->transformed, plane->normal ) - plane->dist; break; } if( dot > light->intensity ) { GL_MarkLights_r( node->children[0], light ); return; } if( dot < -light->intensity ) { GL_MarkLights_r( node->children[1], light ); return; } lightbit = 1 << light->index; face = node->firstFace; count = node->numFaces; while( count-- ) { if( !( face->texinfo->flags & NOLIGHT_MASK ) ) { if( face->dlightframe != glr.drawframe ) { face->dlightframe = glr.drawframe; face->dlightbits = 0; } face->dlightbits |= lightbit; } face++; } GL_MarkLights_r( node->children[0], light ); GL_MarkLights_r( node->children[1], light ); } void GL_MarkLights( void ) { int i; dlight_t *light; for( i = 0, light = glr.fd.dlights; i < glr.fd.num_dlights; i++, light++ ) { light->index = i; VectorCopy( light->origin, light->transformed ); GL_MarkLights_r( r_world.nodes, light ); } } static void GL_TransformLights( bspSubmodel_t *model ) { int i; dlight_t *light; vec3_t temp; if( !model->headnode ) { // this happens on some maps // could lead to a crash of the original ref_gl //Com_WPrintf( "GL_TransformLights: NULL headnode\n" ); return; } for( i = 0, light = glr.fd.dlights; i < glr.fd.num_dlights; i++, light++ ) { light->index = i; VectorSubtract( light->origin, glr.ent->origin, temp ); light->transformed[0] = DotProduct( temp, glr.entaxis[0] ); light->transformed[1] = DotProduct( temp, glr.entaxis[1] ); light->transformed[2] = DotProduct( temp, glr.entaxis[2] ); GL_MarkLights_r( model->headnode, light ); } } void GL_MarkLeaves( void ) { byte fatvis[MAX_MAP_LEAFS/8]; bspLeaf_t *leaf, *lastleaf; bspNode_t *node, *lastnode; byte *vis1, *vis2; uint32 *dst, *src1, *src2; int cluster1, cluster2, longs; vec3_t tmp; static int lastNodesVisible; leaf = Bsp_FindLeaf( glr.fd.vieworg ); cluster1 = cluster2 = leaf->cluster; VectorCopy( glr.fd.vieworg, tmp ); if( !leaf->contents ) { tmp[2] -= 16; } else { tmp[2] += 16; } leaf = Bsp_FindLeaf( tmp ); if( !( leaf->contents & CONTENTS_SOLID ) ) { cluster2 = leaf->cluster; } if( cluster1 == glr.viewcluster1 && cluster2 == glr.viewcluster2 ) { goto finish; } if( gl_lockpvs->integer ) { goto finish; } vis1 = vis2 = Bsp_ClusterPVS( cluster1 ); if( cluster1 != cluster2 ) { vis2 = Bsp_ClusterPVS( cluster2 ); if( !vis1 ) { vis1 = vis2; } else if( !vis2 ) { vis2 = vis1; } } glr.visframe++; lastNodesVisible = 0; glr.viewcluster1 = cluster1; glr.viewcluster2 = cluster2; if( !vis1 || gl_novis->integer ) { /* mark everything visible */ lastleaf = r_world.leafs + r_world.numLeafs; for( leaf = r_world.leafs; leaf != lastleaf; leaf++ ) { leaf->visframe = glr.visframe; } lastnode = r_world.nodes + r_world.numNodes; for( node = r_world.nodes; node != lastnode; node++ ) { node->visframe = glr.visframe; } lastNodesVisible = r_world.numNodes; goto finish; } if( vis1 != vis2 ) { longs = ( r_world.numClusters + 31 ) >> 5; src1 = ( uint32 * )vis1; src2 = ( uint32 * )vis2; dst = ( uint32 * )fatvis; while( longs-- ) { *dst++ = *src1++ | *src2++; } vis1 = fatvis; } lastleaf = r_world.leafs + r_world.numLeafs; for( leaf = r_world.leafs; leaf != lastleaf; leaf++ ) { cluster1 = leaf->cluster; if( cluster1 == -1 ) { continue; } if( Q_IsBitSet( vis1, cluster1 ) ) { node = ( bspNode_t * )leaf; /* mark parent nodes visible */ do { if( node->visframe == glr.visframe ) { break; } node->visframe = glr.visframe; node = node->parent; lastNodesVisible++; } while( node ); } } finish: c.nodesVisible = lastNodesVisible; } #define NODE_CLIPPED 0 #define NODE_UNCLIPPED 15 static qboolean GL_ClipNodeToFrustum( bspNode_t *node, int *clipflags ) { int flags = *clipflags; int i, bits; for( i = 0; i < 4; i++ ) { if( flags & ( 1 << i ) ) { continue; } bits = BoxOnPlaneSide( node->mins, node->maxs, &glr.frustumPlanes[i] ); if( bits == BOX_BEHIND ) { return qfalse; } if( bits == BOX_INFRONT ) { flags |= 1 << i; } } *clipflags = flags; return qtrue; } typedef void (*drawFaceFunc_t)( bspSurface_t *surf ); static drawFaceFunc_t drawFaceFunc; static drawFaceFunc_t drawFaceFuncTable[] = { GL_AddBspSurface, GL_DrawSurfPoly }; static bspSurface_t *alphaFaces; #define BACKFACE_EPSILON 0.001f #define SIDE_FRONT 0 #define SIDE_BACK 1 void GL_DrawBspModel( bspSubmodel_t *model ) { bspSurface_t *face; int count; vec3_t bounds[2]; vec_t dot; cplane_t *plane; vec3_t temp; entity_t *ent = glr.ent; glCullResult_t cull; if( glr.entrotated ) { cull = GL_CullSphere( ent->origin, model->radius ); if( cull == CULL_OUT ) { c.spheresCulled++; return; } if( cull == CULL_CLIP ) { VectorCopy( model->mins, bounds[0] ); VectorCopy( model->maxs, bounds[1] ); cull = GL_CullLocalBox( ent->origin, bounds ); if( cull == CULL_OUT ) { c.rotatedBoxesCulled++; return; } } VectorSubtract( glr.fd.vieworg, ent->origin, temp ); modelViewOrigin[0] = DotProduct( temp, glr.entaxis[0] ); modelViewOrigin[1] = DotProduct( temp, glr.entaxis[1] ); modelViewOrigin[2] = DotProduct( temp, glr.entaxis[2] ); } else { VectorAdd( model->mins, ent->origin, bounds[0] ); VectorAdd( model->maxs, ent->origin, bounds[1] ); cull = GL_CullBox( bounds ); if( cull == CULL_OUT ) { c.boxesCulled++; return; } VectorSubtract( glr.fd.vieworg, ent->origin, modelViewOrigin ); } glr.drawframe++; if( gl_dynamic->integer ) { GL_TransformLights( model ); } /* draw visible faces */ /* FIXME: go by headnode instead? */ face = model->firstFace; count = model->numFaces; while( count-- ) { plane = face->plane; switch( plane->type ) { case PLANE_X: dot = modelViewOrigin[0] - plane->dist; break; case PLANE_Y: dot = modelViewOrigin[1] - plane->dist; break; case PLANE_Z: dot = modelViewOrigin[2] - plane->dist; break; default: dot = DotProduct( modelViewOrigin, plane->normal ) - plane->dist; break; } if( ( dot < -BACKFACE_EPSILON && face->side == SIDE_FRONT ) || ( dot > BACKFACE_EPSILON && face->side == SIDE_BACK ) ) { c.facesCulled++; } else { if( face->texinfo->flags & (SURF_TRANS33|SURF_TRANS66) ) { face->next = alphaFaces; alphaFaces = face; } else { GL_AddBspSurface( face ); } c.facesDrawn++; } face++; } qglPushMatrix(); qglTranslatef( ent->origin[0], ent->origin[1], ent->origin[2] ); if( glr.entrotated ) { qglRotatef( ent->angles[YAW], 0, 0, 1 ); qglRotatef( ent->angles[PITCH], 0, 1, 0 ); qglRotatef( ent->angles[ROLL], 1, 0, 0 ); } GL_SortAndDrawSurfs( qtrue ); qglPopMatrix(); } static void GL_WorldNode_r( bspNode_t *node, int clipflags ) { bspLeaf_t *leaf; bspSurface_t **leafFace, *face; int count, side, area; cplane_t *plane; vec_t dot; uint32 type; if( node->visframe != glr.visframe ) { return; } if( gl_cull_nodes->integer && clipflags != NODE_UNCLIPPED && GL_ClipNodeToFrustum( node, &clipflags ) == qfalse ) { c.nodesCulled++; return; } if( !node->plane ) { /* found a leaf */ leaf = ( bspLeaf_t * )node; if( leaf->contents == CONTENTS_SOLID ) { return; } area = leaf->area; if( !glr.fd.areabits || Q_IsBitSet( glr.fd.areabits, area ) ) { leafFace = leaf->firstLeafFace; count = leaf->numLeafFaces; while( count-- ) { face = *leafFace++; face->drawframe = glr.drawframe; } } return; } plane = node->plane; type = plane->type; if( type < 3 ) { dot = modelViewOrigin[type] - plane->dist; } else { dot = DotProduct( modelViewOrigin, plane->normal ) - plane->dist; } side = dot < 0; GL_WorldNode_r( node->children[side], clipflags ); face = node->firstFace; count = node->numFaces; while( count-- ) { if( face->drawframe == glr.drawframe ) { if( face->side == side ) { if( face->texinfo->flags & (SURF_TRANS33|SURF_TRANS66) ) { face->next = alphaFaces; alphaFaces = face; } else { drawFaceFunc( face ); } c.facesDrawn++; } else { c.facesCulled++; } } face++; } GL_WorldNode_r( node->children[ side ^ 1 ], clipflags ); c.nodesDrawn++; } void GL_DrawWorld( void ) { GL_MarkLeaves(); if( gl_dynamic->integer ) { GL_MarkLights(); } R_ClearSkyBox(); drawFaceFunc = drawFaceFuncTable[gl_primitives->integer & 1]; VectorCopy( glr.fd.vieworg, modelViewOrigin ); GL_WorldNode_r( r_world.nodes, NODE_CLIPPED ); GL_SortAndDrawSurfs( qtrue ); if( !gl_fastsky->integer ) { R_DrawSkyBox(); } } void GL_DrawAlphaFaces( void ) { bspSurface_t *face, *next; face = alphaFaces; if( !face ) { return; } drawFaceFunc = drawFaceFuncTable[gl_primitives->integer & 1]; do { drawFaceFunc( face ); /* Prevent loop condition in case the same face is included twice. * This should never happen normally. */ next = face->next; face->next = NULL; face = next; } while( face ); GL_SortAndDrawSurfs( qfalse ); alphaFaces = NULL; }