cnq3/code/renderer/tr_main.cpp

1512 lines
43 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
===========================================================================
*/
// tr_main.c -- main control flow for each frame
#include "tr_local.h"
trGlobals_t tr;
refimport_t ri;
const float s_flipMatrix[16] = {
// convert from our coordinate system (looking down X)
// to the back-end's coordinate system (looking down -Z)
0, 0, -1, 0,
-1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 0, 1
};
static void R_LocalPointToWorld( const vec3_t local, vec3_t world )
{
world[0] = local[0] * tr.orient.axis[0][0] + local[1] * tr.orient.axis[1][0] + local[2] * tr.orient.axis[2][0] + tr.orient.origin[0];
world[1] = local[0] * tr.orient.axis[0][1] + local[1] * tr.orient.axis[1][1] + local[2] * tr.orient.axis[2][1] + tr.orient.origin[1];
world[2] = local[0] * tr.orient.axis[0][2] + local[1] * tr.orient.axis[1][2] + local[2] * tr.orient.axis[2][2] + tr.orient.origin[2];
}
static void R_LocalNormalToWorld( const vec3_t local, vec3_t world )
{
world[0] = local[0] * tr.orient.axis[0][0] + local[1] * tr.orient.axis[1][0] + local[2] * tr.orient.axis[2][0];
world[1] = local[0] * tr.orient.axis[0][1] + local[1] * tr.orient.axis[1][1] + local[2] * tr.orient.axis[2][1];
world[2] = local[0] * tr.orient.axis[0][2] + local[1] * tr.orient.axis[1][2] + local[2] * tr.orient.axis[2][2];
}
// returns CULL_IN, CULL_CLIP, or CULL_OUT
int R_CullLocalBox( const vec3_t bounds[2] )
{
int i, j;
vec3_t transformed[8];
float dists[8];
vec3_t v;
cplane_t *frust;
int anyBack;
int front, back;
if ( r_nocull->integer ) {
return CULL_CLIP;
}
// transform into world space
for (i = 0 ; i < 8 ; i++) {
v[0] = bounds[i&1][0];
v[1] = bounds[(i>>1)&1][1];
v[2] = bounds[(i>>2)&1][2];
VectorCopy( tr.orient.origin, transformed[i] );
VectorMA( transformed[i], v[0], tr.orient.axis[0], transformed[i] );
VectorMA( transformed[i], v[1], tr.orient.axis[1], transformed[i] );
VectorMA( transformed[i], v[2], tr.orient.axis[2], transformed[i] );
}
// check against frustum planes
anyBack = 0;
for (i = 0 ; i < 4 ; i++) {
frust = &tr.viewParms.frustum[i];
front = back = 0;
for (j = 0 ; j < 8 ; j++) {
dists[j] = DotProduct(transformed[j], frust->normal);
if ( dists[j] > frust->dist ) {
front = 1;
if ( back ) {
break; // a point is in front
}
} else {
back = 1;
}
}
if ( !front ) {
// all points were behind one of the planes
return CULL_OUT;
}
anyBack |= back;
}
if ( !anyBack ) {
return CULL_IN; // completely inside frustum
}
return CULL_CLIP; // partially clipped
}
int R_CullPointAndRadius( const vec3_t pt, float radius )
{
qbool mightBeClipped = qfalse;
if ( r_nocull->integer ) {
return CULL_CLIP;
}
// check against frustum planes
for (int i = 0; i < 4; ++i)
{
const cplane_t* frust = &tr.viewParms.frustum[i];
float dist = DotProduct( pt, frust->normal) - frust->dist;
if ( dist < -radius )
{
return CULL_OUT;
}
else if ( dist <= radius )
{
mightBeClipped = qtrue;
}
}
if ( mightBeClipped )
{
return CULL_CLIP;
}
return CULL_IN; // completely inside frustum
}
int R_CullLocalPointAndRadius( const vec3_t pt, float radius )
{
vec3_t transformed;
R_LocalPointToWorld( pt, transformed );
return R_CullPointAndRadius( transformed, radius );
}
/*
==========================
R_TransformModelToClip
==========================
*/
void R_TransformModelToClip( const vec3_t src, const float *modelMatrix, const float *projectionMatrix,
vec4_t eye, vec4_t dst ) {
int i;
for ( i = 0 ; i < 4 ; i++ ) {
eye[i] =
src[0] * modelMatrix[ i + 0 * 4 ] +
src[1] * modelMatrix[ i + 1 * 4 ] +
src[2] * modelMatrix[ i + 2 * 4 ] +
1 * modelMatrix[ i + 3 * 4 ];
}
for ( i = 0 ; i < 4 ; i++ ) {
dst[i] =
eye[0] * projectionMatrix[ i + 0 * 4 ] +
eye[1] * projectionMatrix[ i + 1 * 4 ] +
eye[2] * projectionMatrix[ i + 2 * 4 ] +
eye[3] * projectionMatrix[ i + 3 * 4 ];
}
}
/*
==========================
R_TransformClipToWindow
==========================
*/
void R_TransformClipToWindow( const vec4_t clip, const viewParms_t *view, vec4_t normalized, vec4_t window ) {
normalized[0] = clip[0] / clip[3];
normalized[1] = clip[1] / clip[3];
normalized[2] = ( clip[2] + clip[3] ) / ( 2 * clip[3] );
window[0] = 0.5f * ( 1.0f + normalized[0] ) * view->viewportWidth;
window[1] = 0.5f * ( 1.0f + normalized[1] ) * view->viewportHeight;
window[2] = normalized[2];
window[0] = (int) ( window[0] + 0.5 );
window[1] = (int) ( window[1] + 0.5 );
}
void R_MultMatrix( const float *a, const float *b, float *out )
{
for ( int i = 0 ; i < 4 ; i++ ) {
for ( int j = 0 ; j < 4 ; j++ ) {
out[ i * 4 + j ] =
a [ i * 4 + 0 ] * b [ 0 * 4 + j ] +
a [ i * 4 + 1 ] * b [ 1 * 4 + j ] +
a [ i * 4 + 2 ] * b [ 2 * 4 + j ] +
a [ i * 4 + 3 ] * b [ 3 * 4 + j ];
}
}
}
void R_MakeIdentityMatrix( float* m )
{
m[ 0] = 1.0f;
m[ 1] = 0.0f;
m[ 2] = 0.0f;
m[ 3] = 0.0f;
m[ 4] = 0.0f;
m[ 5] = 1.0f;
m[ 6] = 0.0f;
m[ 7] = 0.0f;
m[ 8] = 0.0f;
m[ 9] = 0.0f;
m[10] = 1.0f;
m[11] = 0.0f;
m[12] = 0.0f;
m[13] = 0.0f;
m[14] = 0.0f;
m[15] = 1.0f;
}
void R_MakeOrthoProjectionMatrix( float* m, float w, float h )
{
// 2/(r-l) 0 0 0
// 0 2/(t-b) 0 0
// 0 0 1/(zf-zn) 0
// (l+r)/(l-r) (t+b)/(b-t) zn/(zn-zf) 1
const float n = 0.0f;
const float f = 2.0f;
m[ 0] = 2.0f / w;
m[ 4] = 0.0f;
m[ 8] = 0.0f;
m[12] = -1.0f;
m[ 1] = 0.0f;
m[ 5] = -2.0f / h;
m[ 9] = 0.0f;
m[13] = 1.0f;
m[ 2] = 0.0f;
m[ 6] = 0.0f;
m[10] = 1.0f / (f - n);
m[14] = 0.0f;
m[ 3] = 0.0f;
m[ 7] = 0.0f;
m[11] = n / (n - f);
m[15] = 1.0f;
}
/*
=================
R_RotateForEntity
Generates an orientation for an entity and viewParms
Does NOT produce any GL calls
Called by both the front end and the back end
=================
*/
void R_RotateForEntity( const trRefEntity_t* ent, const viewParms_t* viewParms, orientationr_t* orient )
{
float glMatrix[16];
vec3_t delta;
float axisLength;
if ( ent->e.reType != RT_MODEL ) {
*orient = viewParms->world;
return;
}
VectorCopy( ent->e.origin, orient->origin );
VectorCopy( ent->e.axis[0], orient->axis[0] );
VectorCopy( ent->e.axis[1], orient->axis[1] );
VectorCopy( ent->e.axis[2], orient->axis[2] );
glMatrix[0] = orient->axis[0][0];
glMatrix[4] = orient->axis[1][0];
glMatrix[8] = orient->axis[2][0];
glMatrix[12] = orient->origin[0];
glMatrix[1] = orient->axis[0][1];
glMatrix[5] = orient->axis[1][1];
glMatrix[9] = orient->axis[2][1];
glMatrix[13] = orient->origin[1];
glMatrix[2] = orient->axis[0][2];
glMatrix[6] = orient->axis[1][2];
glMatrix[10] = orient->axis[2][2];
glMatrix[14] = orient->origin[2];
glMatrix[3] = 0;
glMatrix[7] = 0;
glMatrix[11] = 0;
glMatrix[15] = 1;
R_MultMatrix( glMatrix, viewParms->world.modelMatrix, orient->modelMatrix );
// calculate the viewer origin in the model's space
// needed for fog, specular, and environment mapping
VectorSubtract( viewParms->orient.origin, orient->origin, delta );
// compensate for scale in the axes if necessary
if ( ent->e.nonNormalizedAxes ) {
axisLength = VectorLength( ent->e.axis[0] );
if ( !axisLength ) {
axisLength = 0;
} else {
axisLength = 1.0f / axisLength;
}
} else {
axisLength = 1.0f;
}
orient->viewOrigin[0] = DotProduct( delta, orient->axis[0] ) * axisLength;
orient->viewOrigin[1] = DotProduct( delta, orient->axis[1] ) * axisLength;
orient->viewOrigin[2] = DotProduct( delta, orient->axis[2] ) * axisLength;
}
// sets up the modelview matrix for a given viewParm
static void R_RotateForViewer()
{
float viewerMatrix[16];
vec3_t origin;
Com_Memset( &tr.orient, 0, sizeof(tr.orient) );
tr.orient.axis[0][0] = 1;
tr.orient.axis[1][1] = 1;
tr.orient.axis[2][2] = 1;
VectorCopy( tr.viewParms.orient.origin, tr.orient.viewOrigin );
// transform by the camera placement
VectorCopy( tr.viewParms.orient.origin, origin );
viewerMatrix[0] = tr.viewParms.orient.axis[0][0];
viewerMatrix[4] = tr.viewParms.orient.axis[0][1];
viewerMatrix[8] = tr.viewParms.orient.axis[0][2];
viewerMatrix[12] = -origin[0] * viewerMatrix[0] + -origin[1] * viewerMatrix[4] + -origin[2] * viewerMatrix[8];
viewerMatrix[1] = tr.viewParms.orient.axis[1][0];
viewerMatrix[5] = tr.viewParms.orient.axis[1][1];
viewerMatrix[9] = tr.viewParms.orient.axis[1][2];
viewerMatrix[13] = -origin[0] * viewerMatrix[1] + -origin[1] * viewerMatrix[5] + -origin[2] * viewerMatrix[9];
viewerMatrix[2] = tr.viewParms.orient.axis[2][0];
viewerMatrix[6] = tr.viewParms.orient.axis[2][1];
viewerMatrix[10] = tr.viewParms.orient.axis[2][2];
viewerMatrix[14] = -origin[0] * viewerMatrix[2] + -origin[1] * viewerMatrix[6] + -origin[2] * viewerMatrix[10];
viewerMatrix[3] = 0;
viewerMatrix[7] = 0;
viewerMatrix[11] = 0;
viewerMatrix[15] = 1;
// convert from our coordinate system (looking down X)
// to the back-end's coordinate system (looking down -Z)
R_MultMatrix( viewerMatrix, s_flipMatrix, tr.orient.modelMatrix );
tr.viewParms.world = tr.orient;
}
static void SetFarClip()
{
// if not rendering the world (icons, menus, etc)
// set a 2k far clip plane
if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) {
tr.viewParms.zFar = 2048;
return;
}
// set far clipping plane dynamically
float farthestCornerDistance = 0;
for (int i = 0; i < 8; ++i)
{
vec3_t v;
v[0] = (i & 1) ? tr.viewParms.visBounds[0][0] : tr.viewParms.visBounds[1][0];
v[1] = (i & 2) ? tr.viewParms.visBounds[0][1] : tr.viewParms.visBounds[1][1];
v[2] = (i & 4) ? tr.viewParms.visBounds[0][2] : tr.viewParms.visBounds[1][2];
float d = DistanceSquared( v, tr.viewParms.orient.origin );
if ( d > farthestCornerDistance )
{
farthestCornerDistance = d;
}
}
tr.viewParms.zFar = sqrt( farthestCornerDistance );
}
static void R_SetupProjection()
{
float width, height, depth;
float zNear, zFar;
// dynamically compute far clip plane distance
SetFarClip();
//
// set up projection matrix
//
zNear = 1.0f;
zFar = tr.viewParms.zFar;
height = 2.0f * zNear * tan( tr.refdef.fov_y * M_PI / 360.0f );
width = 2.0f * zNear * tan( tr.refdef.fov_x * M_PI / 360.0f );
depth = zFar - zNear;
tr.viewParms.projectionMatrix[0] = 2 * zNear / width;
tr.viewParms.projectionMatrix[4] = 0;
tr.viewParms.projectionMatrix[8] = 0;
tr.viewParms.projectionMatrix[12] = 0;
tr.viewParms.projectionMatrix[1] = 0;
tr.viewParms.projectionMatrix[5] = 2 * zNear / height;
tr.viewParms.projectionMatrix[9] = 0;
tr.viewParms.projectionMatrix[13] = 0;
tr.viewParms.projectionMatrix[2] = 0;
tr.viewParms.projectionMatrix[6] = 0;
if ( gal.id == GAL_D3D11 ) {
tr.viewParms.projectionMatrix[10] = -zFar / depth;
tr.viewParms.projectionMatrix[14] = -zFar * zNear / depth;
} else {
tr.viewParms.projectionMatrix[10] = -( zFar + zNear ) / depth;
tr.viewParms.projectionMatrix[14] = -2 * zFar * zNear / depth;
}
tr.viewParms.projectionMatrix[3] = 0;
tr.viewParms.projectionMatrix[7] = 0;
tr.viewParms.projectionMatrix[11] = -1;
tr.viewParms.projectionMatrix[15] = 0;
}
// set up the culling frustum planes for the current view
static void R_SetupFrustum()
{
float ang, xs, xc;
ang = tr.viewParms.fovX / 180 * M_PI * 0.5f;
xs = sin( ang );
xc = cos( ang );
VectorScale( tr.viewParms.orient.axis[0], xs, tr.viewParms.frustum[0].normal );
VectorMA( tr.viewParms.frustum[0].normal, xc, tr.viewParms.orient.axis[1], tr.viewParms.frustum[0].normal );
VectorScale( tr.viewParms.orient.axis[0], xs, tr.viewParms.frustum[1].normal );
VectorMA( tr.viewParms.frustum[1].normal, -xc, tr.viewParms.orient.axis[1], tr.viewParms.frustum[1].normal );
ang = tr.viewParms.fovY / 180 * M_PI * 0.5f;
xs = sin( ang );
xc = cos( ang );
VectorScale( tr.viewParms.orient.axis[0], xs, tr.viewParms.frustum[2].normal );
VectorMA( tr.viewParms.frustum[2].normal, xc, tr.viewParms.orient.axis[2], tr.viewParms.frustum[2].normal );
VectorScale( tr.viewParms.orient.axis[0], xs, tr.viewParms.frustum[3].normal );
VectorMA( tr.viewParms.frustum[3].normal, -xc, tr.viewParms.orient.axis[2], tr.viewParms.frustum[3].normal );
for (int i = 0; i < 4; ++i) {
tr.viewParms.frustum[i].type = PLANE_NON_AXIAL;
tr.viewParms.frustum[i].dist = DotProduct (tr.viewParms.orient.origin, tr.viewParms.frustum[i].normal);
SetPlaneSignbits( &tr.viewParms.frustum[i] );
}
}
static void R_MirrorPoint( const vec3_t in, const orientation_t* surface, const orientation_t* camera, vec3_t out )
{
int i;
vec3_t local;
vec3_t transformed;
float d;
VectorSubtract( in, surface->origin, local );
VectorClear( transformed );
for ( i = 0 ; i < 3 ; i++ ) {
d = DotProduct( local, surface->axis[i] );
VectorMA( transformed, d, camera->axis[i], transformed );
}
VectorAdd( transformed, camera->origin, out );
}
static void R_MirrorVector( const vec3_t in, const orientation_t* surface, const orientation_t* camera, vec3_t out )
{
int i;
float d;
VectorClear( out );
for ( i = 0 ; i < 3 ; i++ ) {
d = DotProduct( in, surface->axis[i] );
VectorMA( out, d, camera->axis[i], out );
}
}
static void R_PlaneForSurface( const surfaceType_t* surfType, cplane_t* plane )
{
vec4_t plane4;
if (!surfType) {
Com_Memset( plane, 0, sizeof(*plane) );
plane->normal[0] = 1;
return;
}
switch (*surfType) {
case SF_FACE:
*plane = ((const srfSurfaceFace_t*)surfType)->plane;
return;
case SF_TRIANGLES: {
const srfTriangles_t* tri = (const srfTriangles_t*)surfType;
const srfVert_t* v1 = tri->verts + tri->indexes[0];
const srfVert_t* v2 = tri->verts + tri->indexes[1];
const srfVert_t* v3 = tri->verts + tri->indexes[2];
PlaneFromPoints( plane4, v1->xyz, v2->xyz, v3->xyz );
VectorCopy( plane4, plane->normal );
plane->dist = plane4[3];
} return;
case SF_POLY: {
const srfPoly_t* poly = (const srfPoly_t*)surfType;
PlaneFromPoints( plane4, poly->verts[0].xyz, poly->verts[1].xyz, poly->verts[2].xyz );
VectorCopy( plane4, plane->normal );
plane->dist = plane4[3];
} return;
default:
Com_Memset( plane, 0, sizeof(*plane) );
plane->normal[0] = 1;
return;
}
}
/*
=================
R_GetPortalOrientation
entityNum is the entity that the portal surface is a part of, which may
be moving and rotating.
Returns qtrue if it should be mirrored
=================
*/
qbool R_GetPortalOrientations( drawSurf_t *drawSurf, int entityNum,
orientation_t *surface, orientation_t *camera,
vec3_t pvsOrigin, qbool *mirror ) {
int i;
cplane_t originalPlane, plane;
trRefEntity_t *e;
float d;
vec3_t transformed;
// create plane axis for the portal we are seeing
R_PlaneForSurface( drawSurf->surface, &originalPlane );
// rotate the plane if necessary
if ( entityNum != ENTITYNUM_WORLD ) {
tr.currentEntityNum = entityNum;
tr.currentEntity = &tr.refdef.entities[entityNum];
// get the orientation of the entity
R_RotateForEntity( tr.currentEntity, &tr.viewParms, &tr.orient );
// rotate the plane, but keep the non-rotated version for matching
// against the portalSurface entities
R_LocalNormalToWorld( originalPlane.normal, plane.normal );
plane.dist = originalPlane.dist + DotProduct( plane.normal, tr.orient.origin );
// translate the original plane
originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.orient.origin );
} else {
plane = originalPlane;
}
VectorCopy( plane.normal, surface->axis[0] );
PerpendicularVector( surface->axis[1], surface->axis[0] );
CrossProduct( surface->axis[0], surface->axis[1], surface->axis[2] );
// locate the portal entity closest to this plane.
// origin will be the origin of the portal, origin2 will be
// the origin of the camera
for ( i = 0 ; i < tr.refdef.num_entities ; i++ ) {
e = &tr.refdef.entities[i];
if ( e->e.reType != RT_PORTALSURFACE ) {
continue;
}
d = DotProduct( e->e.origin, originalPlane.normal ) - originalPlane.dist;
if ( d > 64 || d < -64) {
continue;
}
// get the pvsOrigin from the entity
VectorCopy( e->e.oldorigin, pvsOrigin );
// if the entity is just a mirror, don't use as a camera point
if ( e->e.oldorigin[0] == e->e.origin[0] &&
e->e.oldorigin[1] == e->e.origin[1] &&
e->e.oldorigin[2] == e->e.origin[2] ) {
VectorScale( plane.normal, plane.dist, surface->origin );
VectorCopy( surface->origin, camera->origin );
VectorSubtract( vec3_origin, surface->axis[0], camera->axis[0] );
VectorCopy( surface->axis[1], camera->axis[1] );
VectorCopy( surface->axis[2], camera->axis[2] );
*mirror = qtrue;
return qtrue;
}
// project the origin onto the surface plane to get
// an origin point we can rotate around
d = DotProduct( e->e.origin, plane.normal ) - plane.dist;
VectorMA( e->e.origin, -d, surface->axis[0], surface->origin );
// now get the camera origin and orientation
VectorCopy( e->e.oldorigin, camera->origin );
AxisCopy( e->e.axis, camera->axis );
VectorSubtract( vec3_origin, camera->axis[0], camera->axis[0] );
VectorSubtract( vec3_origin, camera->axis[1], camera->axis[1] );
// optionally rotate
if ( e->e.oldframe ) {
// if a speed is specified
if ( e->e.frame ) {
// continuous rotate
d = (tr.refdef.time/1000.0f) * e->e.frame;
VectorCopy( camera->axis[1], transformed );
RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d );
CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] );
} else {
// bobbing rotate, with skinNum being the rotation offset
d = sin( tr.refdef.time * 0.003f );
d = e->e.skinNum + d * 4;
VectorCopy( camera->axis[1], transformed );
RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d );
CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] );
}
}
else if ( e->e.skinNum ) {
d = e->e.skinNum;
VectorCopy( camera->axis[1], transformed );
RotatePointAroundVector( camera->axis[1], camera->axis[0], transformed, d );
CrossProduct( camera->axis[0], camera->axis[1], camera->axis[2] );
}
*mirror = qfalse;
return qtrue;
}
// if we didn't locate a portal entity, don't render anything.
// We don't want to just treat it as a mirror, because without a
// portal entity the server won't have communicated a proper entity set
// in the snapshot
// unfortunately, with local movement prediction it is easily possible
// to see a surface before the server has communicated the matching
// portal surface entity, so we don't want to print anything here...
//ri.Printf( PRINT_ALL, "Portal surface without a portal entity\n" );
return qfalse;
}
static qbool IsMirror( const drawSurf_t *drawSurf, int entityNum )
{
int i;
cplane_t originalPlane, plane;
float d;
// create plane axis for the portal we are seeing
R_PlaneForSurface( drawSurf->surface, &originalPlane );
// rotate the plane if necessary
if ( entityNum != ENTITYNUM_WORLD )
{
tr.currentEntityNum = entityNum;
tr.currentEntity = &tr.refdef.entities[entityNum];
// get the orientation of the entity
R_RotateForEntity( tr.currentEntity, &tr.viewParms, &tr.orient );
// rotate the plane, but keep the non-rotated version for matching
// against the portalSurface entities
R_LocalNormalToWorld( originalPlane.normal, plane.normal );
plane.dist = originalPlane.dist + DotProduct( plane.normal, tr.orient.origin );
// translate the original plane
originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.orient.origin );
}
else
{
plane = originalPlane;
}
// locate the portal entity closest to this plane.
// origin will be the origin of the portal,
// oldorigin will be the origin of the camera
for ( i = 0 ; i < tr.refdef.num_entities ; i++ )
{
const trRefEntity_t* e = &tr.refdef.entities[i];
if ( e->e.reType != RT_PORTALSURFACE ) {
continue;
}
d = DotProduct( e->e.origin, originalPlane.normal ) - originalPlane.dist;
if ( d > 64 || d < -64) {
continue;
}
// if the entity is just a mirror, don't use as a camera point
return VectorCompare( e->e.origin, e->e.oldorigin );
}
return qfalse;
}
// determines if a surface is COMPLETELY offscreen
static qbool SurfIsOffscreen( const drawSurf_t* drawSurf )
{
float shortest = 100000000;
int entityNum;
int numTriangles;
const shader_t *shader;
int fogNum;
vec4_t clip, eye;
int i;
unsigned int pointAnd = (unsigned int)~0;
R_RotateForViewer();
R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum );
RB_BeginSurface( shader, fogNum );
rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface );
assert( tess.numVertexes < 128 );
for ( i = 0; i < tess.numVertexes; i++ )
{
int j;
unsigned int pointFlags = 0;
R_TransformModelToClip( tess.xyz[i], tr.orient.modelMatrix, tr.viewParms.projectionMatrix, eye, clip );
for ( j = 0; j < 3; j++ )
{
if ( clip[j] >= clip[3] )
{
pointFlags |= (1 << (j*2));
}
else if ( clip[j] <= -clip[3] )
{
pointFlags |= ( 1 << (j*2+1));
}
}
pointAnd &= pointFlags;
}
// trivially reject
if ( pointAnd )
{
return qtrue;
}
// determine if this surface is backfaced and also determine the distance
// to the nearest vertex so we can cull based on portal range. Culling
// based on vertex distance isn't 100% correct (we should be checking for
// range to the surface), but it's good enough for the types of portals
// we have in the game right now.
numTriangles = tess.numIndexes / 3;
for ( i = 0; i < tess.numIndexes; i += 3 )
{
vec3_t normal;
float dot;
float len;
VectorSubtract( tess.xyz[tess.indexes[i]], tr.viewParms.orient.origin, normal );
len = VectorLengthSquared( normal ); // lose the sqrt
if ( len < shortest )
{
shortest = len;
}
if ( ( dot = DotProduct( normal, tess.normal[tess.indexes[i]] ) ) >= 0 )
{
numTriangles--;
}
}
if ( !numTriangles )
{
return qtrue;
}
// mirrors can early out at this point, since we don't do a fade over distance
// with them (although we could)
if ( IsMirror( drawSurf, entityNum ) )
{
return qfalse;
}
if ( shortest > (tess.shader->portalRange*tess.shader->portalRange) )
{
return qtrue;
}
return qfalse;
}
// returns true if another view has been rendered
static qbool R_MirrorViewBySurface( drawSurf_t* drawSurf, int entityNum )
{
viewParms_t newParms;
viewParms_t oldParms;
orientation_t surface, camera;
// don't recursively mirror
if (tr.viewParms.isPortal) {
ri.Printf( PRINT_DEVELOPER, "WARNING: recursive mirror/portal found\n" );
return qfalse;
}
// r_fastsky's "mindless" blit over the entire screen will destroy portal views
if ( r_fastsky->integer || r_noportals->integer ) {
return qfalse;
}
// trivially reject portal/mirror
if ( SurfIsOffscreen( drawSurf ) ) {
return qfalse;
}
// save old viewParms so we can return to it after the mirror view
oldParms = tr.viewParms;
newParms = tr.viewParms;
newParms.isPortal = qtrue;
if ( !R_GetPortalOrientations( drawSurf, entityNum, &surface, &camera,
newParms.pvsOrigin, &newParms.isMirror ) ) {
return qfalse; // bad portal, no portalentity
}
R_MirrorPoint( oldParms.orient.origin, &surface, &camera, newParms.orient.origin );
VectorSubtract( vec3_origin, camera.axis[0], newParms.portalPlane.normal );
newParms.portalPlane.dist = DotProduct( camera.origin, newParms.portalPlane.normal );
R_MirrorVector( oldParms.orient.axis[0], &surface, &camera, newParms.orient.axis[0] );
R_MirrorVector( oldParms.orient.axis[1], &surface, &camera, newParms.orient.axis[1] );
R_MirrorVector( oldParms.orient.axis[2], &surface, &camera, newParms.orient.axis[2] );
// OPTIMIZE: restrict the viewport on the mirrored view
// render the mirror view
R_RenderView( &newParms );
tr.viewParms = oldParms;
return qtrue;
}
// see if a sprite is inside a fog volume
static int R_SpriteFogNum( const trRefEntity_t* ent )
{
int i, j;
if ( tr.refdef.rdflags & RDF_NOWORLDMODEL ) {
return 0;
}
for ( i = 1 ; i < tr.world->numfogs ; i++ ) {
const fog_t* fog = &tr.world->fogs[i];
for ( j = 0 ; j < 3 ; j++ ) {
if ( ent->e.origin[j] - ent->e.radius >= fog->bounds[1][j] ) {
break;
}
if ( ent->e.origin[j] + ent->e.radius <= fog->bounds[0][j] ) {
break;
}
}
if ( j == 3 ) {
return i;
}
}
return 0;
}
/*
==========================================================================================
DRAWSURF SORTING
==========================================================================================
*/
/*
===============
R_Radix
===============
*/
static ID_INLINE void R_Radix( int keyByte, int size, drawSurf_t *source, drawSurf_t *dest )
{
int count[ 256 ] = { 0 };
int index[ 256 ];
int i;
unsigned char *sortKey = NULL;
unsigned char *end = NULL;
sortKey = ( (unsigned char *)&source[ 0 ].sort ) + keyByte;
end = sortKey + ( size * sizeof( drawSurf_t ) );
for( ; sortKey < end; sortKey += sizeof( drawSurf_t ) )
++count[ *sortKey ];
index[ 0 ] = 0;
for( i = 1; i < 256; ++i )
index[ i ] = index[ i - 1 ] + count[ i - 1 ];
sortKey = ( (unsigned char *)&source[ 0 ].sort ) + keyByte;
for( i = 0; i < size; ++i, sortKey += sizeof( drawSurf_t ) )
dest[ index[ *sortKey ]++ ] = source[ i ];
}
/*
===============
R_RadixSort
Radix sort with 4 byte size buckets
===============
*/
static void R_RadixSort( drawSurf_t *source, int size )
{
static drawSurf_t scratch[ MAX_DRAWSURFS ];
#ifdef Q3_LITTLE_ENDIAN
R_Radix( 0, size, source, scratch );
R_Radix( 1, size, scratch, source );
R_Radix( 2, size, source, scratch );
R_Radix( 3, size, scratch, source );
#else
R_Radix( 3, size, source, scratch );
R_Radix( 2, size, scratch, source );
R_Radix( 1, size, source, scratch );
R_Radix( 0, size, scratch, source );
#endif //Q3_LITTLE_ENDIAN
}
// Philip Erdelsky gets all the credit for this one...
static void R_SortLitsurfs( dlight_t* dl )
{
struct litSurf_tape {
litSurf_t *first, *last;
unsigned count;
} tape[4];
// distribute the records alternately to tape[0] and tape[1]
tape[0].count = tape[1].count = 0;
tape[0].first = tape[1].first = NULL;
int base = 0;
litSurf_t* p = dl->head;
while (p) {
litSurf_t* next = p->next;
p->next = tape[base].first;
tape[base].first = p;
tape[base].count++;
p = next;
base ^= 1;
}
// merge from the two active tapes into the two idle ones
// doubling the number of records and pingponging the tape sets as we go
unsigned block_size = 1;
for ( base = 0; tape[base+1].count; base ^= 2, block_size <<= 1 )
{
litSurf_tape* tape0 = tape + base;
litSurf_tape* tape1 = tape + base + 1;
int dest = base ^ 2;
tape[dest].count = tape[dest+1].count = 0;
for (; tape0->count; dest ^= 1)
{
litSurf_tape* output_tape = tape + dest;
unsigned n0, n1;
n0 = n1 = block_size;
while (1)
{
litSurf_tape* chosen_tape;
if (n0 == 0 || tape0->count == 0)
{
if (n1 == 0 || tape1->count == 0)
break;
chosen_tape = tape1;
n1--;
}
else if (n1 == 0 || tape1->count == 0)
{
chosen_tape = tape0;
n0--;
}
else if (tape0->first->sort > tape1->first->sort)
{
chosen_tape = tape1;
n1--;
}
else
{
chosen_tape = tape0;
n0--;
}
chosen_tape->count--;
p = chosen_tape->first;
chosen_tape->first = p->next;
if (output_tape->count == 0)
output_tape->first = p;
else
output_tape->last->next = p;
output_tape->last = p;
output_tape->count++;
}
}
}
if (tape[base].count > 1)
tape[base].last->next = NULL;
dl->head = tape[base].first;
}
///////////////////////////////////////////////////////////////
static float SurfGreyscaleAmount( const shader_t* shader )
{
if (!tr.worldSurface)
return 0.0f;
return shader->greyscaleCTF ? r_mapGreyscaleCTF->value : r_mapGreyscale->value;
}
void R_AddDrawSurf( const surfaceType_t* surface, const shader_t* shader, int fogIndex )
{
if (tr.refdef.numDrawSurfs >= MAX_DRAWSURFS)
return;
drawSurf_t* const drawSurf = &tr.refdef.drawSurfs[tr.refdef.numDrawSurfs++];
drawSurf->sort = R_ComposeSort( tr.currentEntityNum, shader, fogIndex );
drawSurf->surface = surface;
drawSurf->model = tr.currentModel != NULL ? tr.currentModel->index : 0;
drawSurf->shaderNum = shader->index;
drawSurf->greyscale = SurfGreyscaleAmount( shader );
}
void R_AddLitSurf( const surfaceType_t* surface, const shader_t* shader, int fogIndex )
{
if (tr.refdef.numLitSurfs >= MAX_DRAWSURFS)
return;
tr.pc[RF_LIT_SURFS]++;
litSurf_t* const litSurf = &tr.refdef.litSurfs[tr.refdef.numLitSurfs++];
litSurf->sort = R_ComposeSort( tr.currentEntityNum, shader, fogIndex );
litSurf->surface = surface;
litSurf->shaderNum = shader->index;
litSurf->greyscale = SurfGreyscaleAmount( shader );
if (!tr.light->head)
tr.light->head = litSurf;
if (tr.light->tail)
tr.light->tail->next = litSurf;
tr.light->tail = litSurf;
tr.light->tail->next = 0;
}
unsigned int R_ComposeSort( int entityNum, const shader_t *shader, int fogNum )
{
return
(entityNum << QSORT_ENTITYNUM_SHIFT) |
(shader->sortedIndex << QSORT_SHADERNUM_SHIFT) |
(fogNum << QSORT_FOGNUM_SHIFT);
}
void R_DecomposeSort( unsigned sort, int *entityNum, const shader_t **shader, int *fogNum )
{
*fogNum = ( sort >> QSORT_FOGNUM_SHIFT ) & 31;
*shader = tr.sortedShaders[ ( sort >> QSORT_SHADERNUM_SHIFT ) & (MAX_SHADERS-1) ];
*entityNum = ( sort >> QSORT_ENTITYNUM_SHIFT ) & MAX_REFENTITIES;
}
static float R_ComputePointDepth( const vec3_t point, const float* modelMatrix )
{
return -(
modelMatrix[2 + 0 * 4] * point[0] +
modelMatrix[2 + 1 * 4] * point[1] +
modelMatrix[2 + 2 * 4] * point[2] +
modelMatrix[2 + 3 * 4]
);
}
static float R_ComputeEntityPointDepth( const vec3_t point, int entityNum )
{
orientationr_t orient;
if ( entityNum != ENTITYNUM_WORLD )
R_RotateForEntity( &tr.refdef.entities[entityNum], &tr.viewParms, &orient );
else
orient = tr.viewParms.world;
return R_ComputePointDepth( point, orient.modelMatrix );
}
static float R_ComputeSurfaceDepth( const surfaceType_t* surf, int entityNum, qhandle_t model )
{
const float back = 999666.0f;
const float front = -999666.0f;
if ( *surf == SF_ENTITY ) {
const refEntity_t* const ent = &tr.refdef.entities[entityNum].e;
if ( ent->reType == RT_SPRITE ) // CPMA: simple items, rocket explosions, ...
return R_ComputeEntityPointDepth( ent->origin, entityNum );
if ( ent->reType == RT_LIGHTNING ) // CPMA: first-person lightning gun beam
return front;
// note that RT_MODEL not being checked isn't an omission, it's not needed
return back;
}
if ( *surf == SF_POLY ) { // CPMA: impact marks, rocket smoke, ...
const srfPoly_t* const poly = (const srfPoly_t*)surf;
return R_ComputeEntityPointDepth( poly->localOrigin, entityNum );
}
if ( *surf == SF_MD3 ) { // CPMA: spawn points, rocket projectiles, ...
vec3_t mins, maxs, midPoint;
R_ModelBounds( model, mins, maxs );
VectorAdd( mins, maxs, midPoint );
VectorScale( midPoint, 0.5f, midPoint );
return R_ComputeEntityPointDepth( midPoint, entityNum );
}
// If we don't sort them, we let "static" surfaces be drawn behind the "dynamic" ones.
// This helps avoid inconsistent-looking results like CPMA simple items and
// large enough transparent liquid pools (e.g. dropped weapons in the cpm18r acid).
if ( r_transpSort->integer == 0 )
return back;
if ( *surf == SF_FACE ) { // cpm25 water
const srfSurfaceFace_t* const face = (const srfSurfaceFace_t*)surf;
return R_ComputeEntityPointDepth( face->localOrigin, entityNum );
}
if ( *surf == SF_GRID ) { // hektik_b3 item markers
const srfGridMesh_t* const grid = (const srfGridMesh_t*)surf;
return R_ComputeEntityPointDepth( grid->localOrigin, entityNum );
}
if ( *surf == SF_TRIANGLES ) { // cpm18r acid
const srfTriangles_t* const tri = (const srfTriangles_t*)surf;
return R_ComputeEntityPointDepth( tri->localOrigin, entityNum );
}
return back;
}
/*
A few notes on transparency handling:
a) User-specified blend modes and user-created data mean we can't robustly substitute blend modes
with better alternatives that are commutative (e.g. pre-multiplied alpha instead of the "standard" blend).
The amount of corner cases to handle would be a headache and textures would need to be modified.
I'm not even sure if it's possible. I'd gladly be proven wrong, though.
b) The code currently sorts surfaces (triangle groups) back to front.
Sorting individual triangles would obviously be better for many scenarios,
but it would still not be correct at all times (e.g. intersecting triangles, 3-way overlaps).
c) What we really want is true order-independent transparency (OIT).
There are numerous techniques, but this 2-step method is the most promising avenue:
1. Render transparent surfaces into per-pixel linked lists.
2. Do a single "full-screen" pass which sorts and resolves each pixel's list.
This requires support for atomic shader operations.
When atomic shader operations are not available, we could fall back to:
- The current approach.
- Per-pixel fixed-size arrays (there are several methods).
- Depth peeling (there are also several methods).
*/
static int R_CompareDrawSurf( const void* aPtr, const void* bPtr )
{
const drawSurf_t* a = ( const drawSurf_t* )aPtr;
const drawSurf_t* b = ( const drawSurf_t* )bPtr;
if ( a->shaderSort < b->shaderSort )
return -1;
if ( a->shaderSort > b->shaderSort )
return 1;
if ( a->depth > b->depth )
return -1;
if ( a->depth < b->depth )
return 1;
return a->index - b->index;
}
// same thing but ignoring the sort key since some maps get this so wrong
// example: a grate shader in bones_fkd_b4 has a sort key value of 16,
// so it will draw in front of pretty much everything else
static int R_CompareDrawSurfNoKey( const void* aPtr, const void* bPtr )
{
const drawSurf_t* a = ( const drawSurf_t* )aPtr;
const drawSurf_t* b = ( const drawSurf_t* )bPtr;
if ( a->depth > b->depth )
return -1;
if ( a->depth < b->depth )
return 1;
return a->index - b->index;
}
static void R_SortDrawSurfs( int firstDrawSurf, int firstLitSurf )
{
const int numDrawSurfs = tr.refdef.numDrawSurfs - firstDrawSurf;
drawSurf_t* const drawSurfs = tr.refdef.drawSurfs + firstDrawSurf;
// it is possible for some views to not have any surfaces
if ( numDrawSurfs < 1 ) {
// we still need to add it for hyperspace cases
R_AddDrawSurfCmd( drawSurfs, 0, 0 );
return;
}
// sort the drawsurfs by sort type, then shader, then entity, etc
R_RadixSort( drawSurfs, numDrawSurfs );
const shader_t* shader;
int fogNum, entityNum;
// check for any pass through drawing,
// which may cause another view to be rendered first
for ( int i = 0 ; i < numDrawSurfs ; i++ ) {
R_DecomposeSort( (drawSurfs+i)->sort, &entityNum, &shader, &fogNum );
if ( shader->sort > SS_PORTAL ) {
break;
}
// no shader should ever have this sort type
if ( shader->sort == SS_BAD ) {
ri.Error( ERR_DROP, "Shader '%s' with sort == SS_BAD", shader->name );
}
// if the mirror was completely clipped away, we may need to check another surface
if ( R_MirrorViewBySurface( (drawSurfs+i), entityNum) ) {
// this is a debug option to see exactly what is being mirrored
if ( r_portalOnly->integer ) {
return;
}
break; // only one mirror view at a time
}
}
// compute the average camera depth of all transparent surfaces
int numTranspSurfs = 0;
for ( int i = numDrawSurfs - 1; i >= 0; --i ) {
R_DecomposeSort( (drawSurfs+i)->sort, &entityNum, &shader, &fogNum );
if ( shader->sort <= SS_OPAQUE ) {
numTranspSurfs = numDrawSurfs - i - 1;
break;
}
drawSurfs[i].depth = R_ComputeSurfaceDepth( drawSurfs[i].surface, entityNum, drawSurfs[i].model );
drawSurfs[i].index = i;
drawSurfs[i].shaderSort = shader->sort;
}
// sort transparent surfaces by depth
typedef int (*sortFunc_t)( const void*, const void* );
const sortFunc_t transpSort = r_ignoreShaderSortKey->integer ? &R_CompareDrawSurfNoKey : &R_CompareDrawSurf;
qsort( drawSurfs + numDrawSurfs - numTranspSurfs, numTranspSurfs, sizeof(drawSurf_t), transpSort );
#if defined(_DEBUG)
if ( r_ignoreShaderSortKey->integer == 0 ) {
float prevSort = -1.0f;
for ( int i = 0; i < numDrawSurfs; ++i ) {
R_DecomposeSort( (drawSurfs + i)->sort, &entityNum, &shader, &fogNum );
assert( shader->sort >= prevSort );
prevSort = shader->sort;
}
}
#endif
// all the lit surfaces are in a single queue
// but each light's surfaces are sorted within its subsection
for ( int i = 0; i < tr.refdef.num_dlights; ++i ) {
dlight_t* dl = &tr.refdef.dlights[i];
if (dl->head) {
R_SortLitsurfs( dl );
}
}
R_AddDrawSurfCmd( drawSurfs, numDrawSurfs, numTranspSurfs );
}
// entities that will have procedurally generated surfaces will just
// point at this for their sorting surface
static const surfaceType_t entitySurface = SF_ENTITY;
static void R_AddEntitySurfaces()
{
trRefEntity_t* ent;
const shader_t* shader;
if ( !r_drawentities->integer )
return;
for (tr.currentEntityNum = 0; tr.currentEntityNum < tr.refdef.num_entities; ++tr.currentEntityNum) {
ent = tr.currentEntity = &tr.refdef.entities[tr.currentEntityNum];
//
// the weapon model must be handled special --
// we don't want the hacked weapon position showing in mirrors,
// because the true body position will already be drawn
//
if ( (ent->e.renderfx & RF_FIRST_PERSON) && tr.viewParms.isPortal ) {
continue;
}
// simple generated models, like sprites and beams, are not culled
switch ( ent->e.reType ) {
case RT_PORTALSURFACE:
break; // don't draw anything
case RT_SPRITE:
case RT_LIGHTNING:
// self blood sprites, talk balloons, etc should not be drawn in the primary
// view. We can't just do this check for all entities, because md3
// entities may still want to cast shadows from them
if ( (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal ) {
continue;
}
shader = R_GetShaderByHandle( ent->e.customShader );
R_AddDrawSurf( &entitySurface, shader, R_SpriteFogNum( ent ) );
break;
case RT_MODEL:
// we must set up parts of tr.orient for model culling
R_RotateForEntity( ent, &tr.viewParms, &tr.orient );
tr.currentModel = R_GetModelByHandle( ent->e.hModel );
if (!tr.currentModel) {
R_AddDrawSurf( &entitySurface, tr.defaultShader, 0 );
} else {
switch ( tr.currentModel->type ) {
case MOD_MD3:
R_AddMD3Surfaces( ent );
break;
case MOD_BRUSH:
// we want doors and lifts to be considered world surfaces too
tr.worldSurface = qtrue;
R_AddBrushModelSurfaces( ent );
tr.worldSurface = qfalse;
break;
case MOD_BAD: // null model axis
if ( (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal)
break;
R_AddDrawSurf( &entitySurface, tr.defaultShader, 0 );
break;
default:
ri.Error( ERR_DROP, "R_AddEntitySurfaces: Bad modeltype" );
break;
}
}
break;
default:
ri.Error( ERR_DROP, "R_AddEntitySurfaces: Bad reType" );
}
}
}
static void R_GenerateDrawSurfs()
{
tr.worldSurface = qtrue;
R_AddWorldSurfaces();
tr.worldSurface = qfalse;
R_AddPolygonSurfaces();
// set the projection matrix with the minimum zfar
// now that we have the world bounded
// this needs to be done before entities are added,
// because they use the projection matrix for lod calculation
R_SetupProjection();
R_AddEntitySurfaces();
}
int re_cameraMatrixTime;
// a view may be either the actual camera view, or a mirror / remote location
void R_RenderView( const viewParms_t* parms )
{
if ( parms->viewportWidth <= 0 || parms->viewportHeight <= 0 )
return;
tr.viewCount++;
tr.viewParms = *parms;
tr.viewParms.frameSceneNum = tr.frameSceneNum;
tr.viewParms.frameCount = tr.frameCount;
const int firstDrawSurf = tr.refdef.numDrawSurfs;
const int firstLitSurf = tr.refdef.numLitSurfs;
// set viewParms.world
re_cameraMatrixTime = Sys_Milliseconds();
R_RotateForViewer();
R_SetupFrustum();
R_GenerateDrawSurfs();
R_SortDrawSurfs( firstDrawSurf, firstLitSurf );
}
const image_t* R_UpdateAndGetBundleImage( const textureBundle_t* bundle, updateAnimatedImage_t updateImage )
{
if ( bundle->isVideoMap ) {
ri.CIN_RunCinematic( bundle->videoMapHandle );
int w, h, client;
const byte* data;
qbool dirty;
const qbool validData = ri.CIN_GrabCinematic( bundle->videoMapHandle, &w, &h, &data, &client, &dirty );
if ( client >= 0 && client < ARRAY_LEN( tr.scratchImage ) ) {
image_t* const image = tr.scratchImage[client];
if ( validData )
(*updateImage)(image, w, h, data, dirty);
return image;
} else {
return tr.whiteImage;
}
}
if ( bundle->numImageAnimations <= 1 )
return bundle->image[0];
// it is necessary to do this messy calc to make sure animations line up
// exactly with waveforms of the same frequency
double v = tess.shaderTime * bundle->imageAnimationSpeed * FUNCTABLE_SIZE;
long long int index = v;
index >>= FUNCTABLE_SHIFT;
if ( index < 0 ) // may happen with shader time offsets
return bundle->image[0];
return bundle->image[index % bundle->numImageAnimations];
}