mirror of
https://bitbucket.org/CPMADevs/cnq3
synced 2024-11-27 14:22:32 +00:00
1507 lines
42 KiB
C++
1507 lines
42 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;
|
|
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];
|
|
}
|