mirror of
https://github.com/openmoh/openmohaa.git
synced 2025-04-28 13:47:58 +03:00
1809 lines
45 KiB
C
1809 lines
45 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 Foobar; 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"
|
|
#include "tiki.h"
|
|
|
|
trGlobals_t tr;
|
|
|
|
static float s_flipMatrix[16] = {
|
|
// convert from our coordinate system (looking down X)
|
|
// to OpenGL's coordinate system (looking down -Z)
|
|
0, 0, -1, 0,
|
|
-1, 0, 0, 0,
|
|
0, 1, 0, 0,
|
|
0, 0, 0, 1
|
|
};
|
|
|
|
|
|
refimport_t ri;
|
|
|
|
// entities that will have procedurally generated surfaces will just
|
|
// point at this for their sorting surface
|
|
surfaceType_t entitySurface = SF_ENTITY;
|
|
|
|
/*
|
|
=================
|
|
R_CullLocalBox
|
|
|
|
Returns CULL_IN, CULL_CLIP, or CULL_OUT
|
|
=================
|
|
*/
|
|
int R_CullLocalBoxOffset(const vec3_t offset, 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.ori.origin, transformed[i] );
|
|
VectorAdd(transformed[i], offset, transformed[i]);
|
|
VectorMA( transformed[i], v[0], tr.ori.axis[0], transformed[i] );
|
|
VectorMA( transformed[i], v[1], tr.ori.axis[1], transformed[i] );
|
|
VectorMA( transformed[i], v[2], tr.ori.axis[2], transformed[i] );
|
|
}
|
|
|
|
// check against frustum planes
|
|
anyBack = 0;
|
|
for (i = 0 ; i < tr.viewParms.fog.extrafrustums + 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_CullLocalBox(vec3_t bounds[2]) {
|
|
return R_CullLocalBoxOffset(vec3_origin, bounds);
|
|
}
|
|
|
|
/*
|
|
** R_CullLocalPointAndRadius
|
|
*/
|
|
int R_CullLocalPointAndRadius( vec3_t pt, float radius )
|
|
{
|
|
vec3_t transformed;
|
|
|
|
R_LocalPointToWorld( pt, transformed );
|
|
|
|
return R_CullPointAndRadius( transformed, radius );
|
|
}
|
|
|
|
/*
|
|
** R_CullPointAndRadius
|
|
*/
|
|
int R_CullPointAndRadius( vec3_t pt, float radius )
|
|
{
|
|
int i;
|
|
float dist;
|
|
cplane_t *frust;
|
|
qboolean mightBeClipped = qfalse;
|
|
|
|
if ( r_nocull->integer ) {
|
|
return CULL_CLIP;
|
|
}
|
|
|
|
// check against frustum planes
|
|
for (i = 0 ; i < tr.viewParms.fog.extrafrustums + 4; i++)
|
|
{
|
|
frust = &tr.viewParms.frustum[i];
|
|
|
|
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_DistanceCullLocalPointAndRadius(float fDist, const vec3_t pt, float radius) {
|
|
vec3_t transformed;
|
|
|
|
R_LocalPointToWorld(pt, transformed);
|
|
|
|
return R_DistanceCullPointAndRadius(fDist, transformed, radius);
|
|
}
|
|
|
|
int R_DistanceCullPointAndRadius(float fDist, const vec3_t pt, float radius) {
|
|
if (!r_nocull->integer)
|
|
{
|
|
vec3_t vDelta;
|
|
float fLengthSquared;
|
|
float fTotalDistSquared;
|
|
|
|
VectorSubtract(pt, tr.viewParms.ori.origin, vDelta);
|
|
fLengthSquared = VectorLengthSquared(vDelta);
|
|
fTotalDistSquared = Square(fDist + radius);
|
|
|
|
if (Square(fDist + radius) < Square(vDelta[2]) + fLengthSquared) {
|
|
return CULL_OUT;
|
|
} else if (Square(fDist - radius) > Square(vDelta[2]) + fLengthSquared) {
|
|
return CULL_IN;
|
|
}
|
|
}
|
|
|
|
return CULL_CLIP;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_LocalNormalToWorld
|
|
|
|
=================
|
|
*/
|
|
void R_LocalNormalToWorld (const vec3_t local, vec3_t world) {
|
|
world[0] = local[0] * tr.ori.axis[0][0] + local[1] * tr.ori.axis[1][0] + local[2] * tr.ori.axis[2][0];
|
|
world[1] = local[0] * tr.ori.axis[0][1] + local[1] * tr.ori.axis[1][1] + local[2] * tr.ori.axis[2][1];
|
|
world[2] = local[0] * tr.ori.axis[0][2] + local[1] * tr.ori.axis[1][2] + local[2] * tr.ori.axis[2][2];
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_LocalPointToWorld
|
|
|
|
=================
|
|
*/
|
|
void R_LocalPointToWorld (const vec3_t local, vec3_t world) {
|
|
world[0] = local[0] * tr.ori.axis[0][0] + local[1] * tr.ori.axis[1][0] + local[2] * tr.ori.axis[2][0] + tr.ori.origin[0];
|
|
world[1] = local[0] * tr.ori.axis[0][1] + local[1] * tr.ori.axis[1][1] + local[2] * tr.ori.axis[2][1] + tr.ori.origin[1];
|
|
world[2] = local[0] * tr.ori.axis[0][2] + local[1] * tr.ori.axis[1][2] + local[2] * tr.ori.axis[2][2] + tr.ori.origin[2];
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_WorldToLocal
|
|
|
|
=================
|
|
*/
|
|
void R_WorldToLocal (vec3_t world, vec3_t local) {
|
|
local[0] = DotProduct(world, tr.ori.axis[0]);
|
|
local[1] = DotProduct(world, tr.ori.axis[1]);
|
|
local[2] = DotProduct(world, tr.ori.axis[2]);
|
|
}
|
|
|
|
/*
|
|
==========================
|
|
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 );
|
|
}
|
|
|
|
|
|
/*
|
|
==========================
|
|
myGlMultMatrix
|
|
|
|
==========================
|
|
*/
|
|
void myGlMultMatrix( const float *a, const float *b, float *out ) {
|
|
int i, j;
|
|
|
|
for ( i = 0 ; i < 4 ; i++ ) {
|
|
for ( 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_AdjustVisBoundsForSprite(refSprite_t* ent, viewParms_t* viewParms, orientationr_t* or )
|
|
{
|
|
if (tr.viewParms.visBounds[0][0] > ent->origin[0] - ent->scale) {
|
|
tr.viewParms.visBounds[0][0] = ent->origin[0] - ent->scale;
|
|
}
|
|
if (tr.viewParms.visBounds[0][1] > ent->origin[1] - ent->scale) {
|
|
tr.viewParms.visBounds[0][1] = ent->origin[1] - ent->scale;
|
|
}
|
|
if (tr.viewParms.visBounds[0][2] > ent->origin[2] - ent->scale) {
|
|
tr.viewParms.visBounds[0][2] = ent->origin[2] - ent->scale;
|
|
}
|
|
|
|
if (tr.viewParms.visBounds[1][0] < ent->origin[0] + ent->scale) {
|
|
tr.viewParms.visBounds[1][0] = ent->origin[0] + ent->scale;
|
|
}
|
|
if (tr.viewParms.visBounds[1][1] < ent->origin[1] + ent->scale) {
|
|
tr.viewParms.visBounds[1][1] = ent->origin[1] + ent->scale;
|
|
}
|
|
if (tr.viewParms.visBounds[1][2] < ent->origin[2] + ent->scale) {
|
|
tr.viewParms.visBounds[1][2] = ent->origin[2] + ent->scale;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
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 *ori ) {
|
|
float glMatrix[16];
|
|
vec3_t delta;
|
|
float axisLength;
|
|
|
|
if ( ent->e.reType != RT_MODEL ) {
|
|
*ori = viewParms->world;
|
|
return;
|
|
}
|
|
|
|
VectorCopy( ent->e.origin, ori->origin );
|
|
|
|
VectorCopy( ent->e.axis[0], ori->axis[0] );
|
|
VectorCopy( ent->e.axis[1], ori->axis[1] );
|
|
VectorCopy( ent->e.axis[2], ori->axis[2] );
|
|
|
|
glMatrix[0] = ori->axis[0][0];
|
|
glMatrix[4] = ori->axis[1][0];
|
|
glMatrix[8] = ori->axis[2][0];
|
|
glMatrix[12] = ori->origin[0];
|
|
|
|
glMatrix[1] = ori->axis[0][1];
|
|
glMatrix[5] = ori->axis[1][1];
|
|
glMatrix[9] = ori->axis[2][1];
|
|
glMatrix[13] = ori->origin[1];
|
|
|
|
glMatrix[2] = ori->axis[0][2];
|
|
glMatrix[6] = ori->axis[1][2];
|
|
glMatrix[10] = ori->axis[2][2];
|
|
glMatrix[14] = ori->origin[2];
|
|
|
|
glMatrix[3] = 0;
|
|
glMatrix[7] = 0;
|
|
glMatrix[11] = 0;
|
|
glMatrix[15] = 1;
|
|
|
|
myGlMultMatrix( glMatrix, viewParms->world.modelMatrix, ori->modelMatrix );
|
|
|
|
// calculate the viewer origin in the model's space
|
|
// needed for fog, specular, and environment mapping
|
|
VectorSubtract( viewParms->ori.origin, ori->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;
|
|
}
|
|
|
|
ori->viewOrigin[0] = DotProduct( delta, ori->axis[0] ) * axisLength;
|
|
ori->viewOrigin[1] = DotProduct( delta, ori->axis[1] ) * axisLength;
|
|
ori->viewOrigin[2] = DotProduct( delta, ori->axis[2] ) * axisLength;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_RotateForStaticModel
|
|
|
|
Generates an orientation for a static model and viewParms
|
|
=================
|
|
*/
|
|
void R_RotateForStaticModel( cStaticModelUnpacked_t *SM, const viewParms_t *viewParms,
|
|
orientationr_t *ori ) {
|
|
float glMatrix[16];
|
|
vec3_t delta;
|
|
float tiki_scale;
|
|
|
|
tiki_scale = SM->tiki->load_scale * SM->scale;
|
|
|
|
VectorCopy( SM->origin, ori->origin );
|
|
|
|
VectorCopy( SM->axis[0], ori->axis[0] );
|
|
VectorCopy( SM->axis[1], ori->axis[1] );
|
|
VectorCopy( SM->axis[2], ori->axis[2] );
|
|
|
|
glMatrix[0] = ori->axis[0][0] * tiki_scale;
|
|
glMatrix[4] = ori->axis[1][0] * tiki_scale;
|
|
glMatrix[8] = ori->axis[2][0] * tiki_scale;
|
|
glMatrix[12] = ori->origin[0];
|
|
|
|
glMatrix[1] = ori->axis[0][1] * tiki_scale;
|
|
glMatrix[5] = ori->axis[1][1] * tiki_scale;
|
|
glMatrix[9] = ori->axis[2][1] * tiki_scale;
|
|
glMatrix[13] = ori->origin[1];
|
|
|
|
glMatrix[2] = ori->axis[0][2] * tiki_scale;
|
|
glMatrix[6] = ori->axis[1][2] * tiki_scale;
|
|
glMatrix[10] = ori->axis[2][2] * tiki_scale;
|
|
glMatrix[14] = ori->origin[2];
|
|
|
|
glMatrix[3] = 0;
|
|
glMatrix[7] = 0;
|
|
glMatrix[11] = 0;
|
|
glMatrix[15] = 1;
|
|
|
|
myGlMultMatrix( glMatrix, viewParms->world.modelMatrix, ori->modelMatrix );
|
|
|
|
// calculate the viewer origin in the model's space
|
|
// needed for fog, specular, and environment mapping
|
|
VectorSubtract( viewParms->ori.origin, ori->origin, delta );
|
|
|
|
ori->viewOrigin[0] = DotProduct( delta, ori->axis[0] );
|
|
ori->viewOrigin[1] = DotProduct( delta, ori->axis[1] );
|
|
ori->viewOrigin[2] = DotProduct( delta, ori->axis[2] );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_RotateForViewer
|
|
|
|
Sets up the modelview matrix for a given viewParm
|
|
=================
|
|
*/
|
|
void R_RotateForViewer (void)
|
|
{
|
|
float viewerMatrix[16];
|
|
vec3_t origin;
|
|
|
|
Com_Memset (&tr.ori, 0, sizeof(tr.ori));
|
|
tr.ori.axis[0][0] = 1;
|
|
tr.ori.axis[1][1] = 1;
|
|
tr.ori.axis[2][2] = 1;
|
|
VectorCopy (tr.viewParms.ori.origin, tr.ori.viewOrigin);
|
|
|
|
// transform by the camera placement
|
|
VectorCopy( tr.viewParms.ori.origin, origin );
|
|
|
|
viewerMatrix[0] = tr.viewParms.ori.axis[0][0];
|
|
viewerMatrix[4] = tr.viewParms.ori.axis[0][1];
|
|
viewerMatrix[8] = tr.viewParms.ori.axis[0][2];
|
|
viewerMatrix[12] = -origin[0] * viewerMatrix[0] + -origin[1] * viewerMatrix[4] + -origin[2] * viewerMatrix[8];
|
|
|
|
viewerMatrix[1] = tr.viewParms.ori.axis[1][0];
|
|
viewerMatrix[5] = tr.viewParms.ori.axis[1][1];
|
|
viewerMatrix[9] = tr.viewParms.ori.axis[1][2];
|
|
viewerMatrix[13] = -origin[0] * viewerMatrix[1] + -origin[1] * viewerMatrix[5] + -origin[2] * viewerMatrix[9];
|
|
|
|
viewerMatrix[2] = tr.viewParms.ori.axis[2][0];
|
|
viewerMatrix[6] = tr.viewParms.ori.axis[2][1];
|
|
viewerMatrix[10] = tr.viewParms.ori.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 OpenGL's coordinate system (looking down -Z)
|
|
myGlMultMatrix( viewerMatrix, s_flipMatrix, tr.ori.modelMatrix );
|
|
|
|
tr.viewParms.world = tr.ori;
|
|
|
|
}
|
|
|
|
/*
|
|
** SetFarClip
|
|
*/
|
|
static void SetFarClip( void )
|
|
{
|
|
float farthestCornerDistance = 0;
|
|
int i;
|
|
|
|
// 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 planes dynamically
|
|
//
|
|
farthestCornerDistance = 0;
|
|
for ( i = 0; i < 8; i++ )
|
|
{
|
|
vec3_t v;
|
|
vec3_t vecTo;
|
|
float distance;
|
|
|
|
if ( i & 1 )
|
|
{
|
|
v[0] = tr.viewParms.visBounds[0][0];
|
|
}
|
|
else
|
|
{
|
|
v[0] = tr.viewParms.visBounds[1][0];
|
|
}
|
|
|
|
if ( i & 2 )
|
|
{
|
|
v[1] = tr.viewParms.visBounds[0][1];
|
|
}
|
|
else
|
|
{
|
|
v[1] = tr.viewParms.visBounds[1][1];
|
|
}
|
|
|
|
if ( i & 4 )
|
|
{
|
|
v[2] = tr.viewParms.visBounds[0][2];
|
|
}
|
|
else
|
|
{
|
|
v[2] = tr.viewParms.visBounds[1][2];
|
|
}
|
|
|
|
VectorSubtract( v, tr.viewParms.ori.origin, vecTo );
|
|
|
|
distance = vecTo[0] * vecTo[0] + vecTo[1] * vecTo[1] + vecTo[2] * vecTo[2];
|
|
|
|
if ( distance > farthestCornerDistance )
|
|
{
|
|
farthestCornerDistance = distance;
|
|
}
|
|
}
|
|
tr.viewParms.zFar = sqrt( farthestCornerDistance );
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
R_SetupProjection
|
|
===============
|
|
*/
|
|
void R_SetupProjection( void ) {
|
|
float xmin, xmax, ymin, ymax;
|
|
float width, height, depth;
|
|
float zNear, zFar;
|
|
|
|
// dynamically compute far clip plane distance
|
|
SetFarClip();
|
|
|
|
//
|
|
// set up projection matrix
|
|
//
|
|
zNear = r_znear->value;
|
|
zFar = tr.viewParms.zFar;
|
|
|
|
ymax = zNear * tan( tr.refdef.fov_y * M_PI / 360.0f );
|
|
ymin = -ymax;
|
|
|
|
xmax = zNear * tan( tr.refdef.fov_x * M_PI / 360.0f );
|
|
xmin = -xmax;
|
|
|
|
width = xmax - xmin;
|
|
height = ymax - ymin;
|
|
depth = zFar - zNear;
|
|
|
|
tr.viewParms.projectionMatrix[0] = 2 * zNear / width;
|
|
tr.viewParms.projectionMatrix[4] = 0;
|
|
tr.viewParms.projectionMatrix[8] = ( xmax + xmin ) / width; // normally 0
|
|
tr.viewParms.projectionMatrix[12] = 0;
|
|
|
|
tr.viewParms.projectionMatrix[1] = 0;
|
|
tr.viewParms.projectionMatrix[5] = 2 * zNear / height;
|
|
tr.viewParms.projectionMatrix[9] = ( ymax + ymin ) / height; // normally 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;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_SetupFrustum
|
|
|
|
Setup that culling frustum planes for the current view
|
|
=================
|
|
*/
|
|
void R_SetupFrustum (void) {
|
|
int i;
|
|
float xs, xc;
|
|
float ang;
|
|
|
|
ang = tr.viewParms.fovX / 180 * M_PI * 0.5f;
|
|
xs = sin( ang );
|
|
xc = cos( ang );
|
|
|
|
VectorScale( tr.viewParms.ori.axis[0], xs, tr.viewParms.frustum[0].normal );
|
|
VectorMA( tr.viewParms.frustum[0].normal, xc, tr.viewParms.ori.axis[1], tr.viewParms.frustum[0].normal );
|
|
|
|
VectorScale( tr.viewParms.ori.axis[0], xs, tr.viewParms.frustum[1].normal );
|
|
VectorMA( tr.viewParms.frustum[1].normal, -xc, tr.viewParms.ori.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.ori.axis[0], xs, tr.viewParms.frustum[2].normal );
|
|
VectorMA( tr.viewParms.frustum[2].normal, xc, tr.viewParms.ori.axis[2], tr.viewParms.frustum[2].normal );
|
|
|
|
VectorScale( tr.viewParms.ori.axis[0], xs, tr.viewParms.frustum[3].normal );
|
|
VectorMA( tr.viewParms.frustum[3].normal, -xc, tr.viewParms.ori.axis[2], tr.viewParms.frustum[3].normal );
|
|
|
|
for (i=0 ; i<4 ; i++) {
|
|
tr.viewParms.frustum[i].type = PLANE_NON_AXIAL;
|
|
tr.viewParms.frustum[i].dist = DotProduct (tr.viewParms.ori.origin, tr.viewParms.frustum[i].normal);
|
|
SetPlaneSignbits( &tr.viewParms.frustum[i] );
|
|
}
|
|
|
|
if (tr.viewParms.isPortalSky) {
|
|
// since 2.0: skybox farplane
|
|
if (r_skybox_farplane->integer) {
|
|
tr.viewParms.farplane_distance = r_skybox_farplane->value;
|
|
|
|
sscanf(
|
|
r_farplane_color->string,
|
|
"%f %f %f",
|
|
&tr.viewParms.farplane_color[0],
|
|
&tr.viewParms.farplane_color[1],
|
|
&tr.viewParms.farplane_color[2]
|
|
);
|
|
|
|
if (r_farplane_nocull->integer > 0) {
|
|
tr.viewParms.farplane_cull = 0;
|
|
} else if (r_farplane_nocull->integer == 0) {
|
|
tr.viewParms.farplane_cull = 1;
|
|
} else {
|
|
tr.viewParms.farplane_cull = 2;
|
|
}
|
|
}
|
|
}
|
|
else if (r_farplane->integer)
|
|
{
|
|
tr.viewParms.farplane_distance = r_farplane->value;
|
|
tr.viewParms.farplane_bias = r_farplane_bias->value;
|
|
|
|
sscanf(
|
|
r_farplane_color->string,
|
|
"%f %f %f",
|
|
&tr.viewParms.farplane_color[0],
|
|
&tr.viewParms.farplane_color[1],
|
|
&tr.viewParms.farplane_color[2]);
|
|
|
|
if (r_farplane_nocull->integer > 0) {
|
|
tr.viewParms.farplane_cull = 0;
|
|
} else if (r_farplane_nocull->integer == 0) {
|
|
tr.viewParms.farplane_cull = 1;
|
|
} else {
|
|
tr.viewParms.farplane_cull = 2;
|
|
}
|
|
}
|
|
|
|
if (tr.viewParms.farplane_distance)
|
|
{
|
|
vec3_t farPoint;
|
|
float realPlaneLen;
|
|
float tmp;
|
|
vec4_t fogColor;
|
|
|
|
tr.viewParms.fog.len = tr.viewParms.farplane_distance;
|
|
tr.viewParms.fog.oolen = 1.0 / tr.viewParms.farplane_distance;
|
|
VectorMA(tr.viewParms.ori.origin, tr.viewParms.farplane_distance, tr.viewParms.ori.axis[0], farPoint);
|
|
|
|
VectorNegate(tr.viewParms.ori.axis[0], tr.viewParms.frustum[4].normal);
|
|
tr.viewParms.frustum[4].type = PLANE_NON_AXIAL;
|
|
tr.viewParms.frustum[4].dist = DotProduct(farPoint, tr.viewParms.frustum[4].normal);
|
|
SetPlaneSignbits(&tr.viewParms.frustum[4]);
|
|
|
|
tr.viewParms.fog.enabled = r_farplane_nofog->integer == 0;
|
|
if (tr.viewParms.farplane_cull) {
|
|
// extra fustum for culling
|
|
tr.viewParms.fog.extrafrustums = 1;
|
|
} else {
|
|
tr.viewParms.fog.extrafrustums = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tr.viewParms.fog.enabled = 0;
|
|
tr.viewParms.fog.extrafrustums = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
R_MirrorPoint
|
|
=================
|
|
*/
|
|
void R_MirrorPoint (vec3_t in, orientation_t *surface, 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 );
|
|
}
|
|
|
|
void R_MirrorVector (vec3_t in, orientation_t *surface, 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 );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=============
|
|
R_PlaneForSurface
|
|
=============
|
|
*/
|
|
void R_PlaneForSurface (surfaceType_t *surfType, cplane_t *plane) {
|
|
srfTriangles_t *tri;
|
|
srfPoly_t *poly;
|
|
drawVert_t *v1, *v2, *v3;
|
|
vec4_t plane4;
|
|
|
|
if (!surfType) {
|
|
Com_Memset (plane, 0, sizeof(*plane));
|
|
plane->normal[0] = 1;
|
|
return;
|
|
}
|
|
switch (*surfType) {
|
|
case SF_FACE:
|
|
*plane = ((srfSurfaceFace_t *)surfType)->plane;
|
|
return;
|
|
case SF_TRIANGLES:
|
|
tri = (srfTriangles_t *)surfType;
|
|
v1 = tri->verts + tri->indexes[0];
|
|
v2 = tri->verts + tri->indexes[1];
|
|
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:
|
|
poly = (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
|
|
=================
|
|
*/
|
|
qboolean R_GetPortalOrientations( drawSurf_t *drawSurf, int entityNum,
|
|
orientation_t *surface, orientation_t *camera,
|
|
vec3_t pvsOrigin, qboolean *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.ori );
|
|
|
|
// 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.ori.origin );
|
|
|
|
// translate the original plane
|
|
originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.ori.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.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 qboolean IsMirror( const drawSurf_t *drawSurf, int entityNum )
|
|
{
|
|
int i;
|
|
cplane_t originalPlane, plane;
|
|
trRefEntity_t *e;
|
|
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.ori );
|
|
|
|
// 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.ori.origin );
|
|
|
|
// translate the original plane
|
|
originalPlane.dist = originalPlane.dist + DotProduct( originalPlane.normal, tr.ori.origin );
|
|
}
|
|
else
|
|
{
|
|
plane = originalPlane;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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] )
|
|
{
|
|
return qtrue;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
** SurfIsOffscreen
|
|
**
|
|
** Determines if a surface is completely offscreen.
|
|
*/
|
|
qboolean SurfIsOffscreen(const srfSurfaceFace_t* surface, shader_t* shader, int entityNum) {
|
|
float shortest = 100000000;
|
|
int numTriangles;
|
|
qboolean doRange;
|
|
orientationr_t surfOr;
|
|
unsigned int* indices;
|
|
vec4_t clip, eye;
|
|
int i;
|
|
unsigned int pointOr = 0;
|
|
unsigned int pointAnd = (unsigned int)~0;
|
|
|
|
if (surface->surfaceType != SF_FACE) {
|
|
ri.Printf(PRINT_WARNING, "WARNING: SurfIsOffscreen called on non-bmodel!\n");
|
|
return qfalse;
|
|
}
|
|
|
|
if ( glConfig.smpActive ) { // FIXME! we can't do RB_BeginSurface/RB_EndSurface stuff with smp!
|
|
return qfalse;
|
|
}
|
|
|
|
if (entityNum == ENTITYNUM_WORLD) {
|
|
surfOr = tr.viewParms.world;
|
|
} else {
|
|
R_RotateForEntity(&tr.refdef.entities[entityNum], &tr.viewParms, &surfOr);
|
|
}
|
|
|
|
for ( i = 0; i < surface->numPoints; i++ )
|
|
{
|
|
int j;
|
|
unsigned int pointFlags = 0;
|
|
|
|
R_TransformModelToClip( surface->points[i], surfOr.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;
|
|
pointOr |= 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 = surface->numIndices / 3;
|
|
|
|
for ( i = 0; i < surface->numIndices; i += 3 )
|
|
{
|
|
vec3_t normal;
|
|
float dot;
|
|
float len;
|
|
unsigned* indices;
|
|
|
|
indices = (unsigned*)(((char*)surface) + surface->ofsIndices);
|
|
|
|
VectorSubtract( surface->points[indices[i]], surfOr.viewOrigin, normal);
|
|
|
|
if (shader->fDistRange > 0) {
|
|
len = VectorLengthSquared(normal); // lose the sqrt
|
|
if (len < shortest)
|
|
{
|
|
shortest = len;
|
|
}
|
|
}
|
|
|
|
if ( ( dot = DotProduct( normal, surface->plane.normal ) ) >= 0 )
|
|
{
|
|
numTriangles--;
|
|
}
|
|
}
|
|
if ( !numTriangles )
|
|
{
|
|
return qtrue;
|
|
}
|
|
|
|
if (shader->fDistRange > 0.0)
|
|
{
|
|
if (shortest > Square(shader->fDistRange))
|
|
{
|
|
return qtrue;
|
|
}
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
static qboolean DrawSurfIsOffscreen(drawSurf_t* drawSurf) {
|
|
int entityNum;
|
|
shader_t* shader;
|
|
int dlighted;
|
|
qboolean bStaticModel;
|
|
|
|
R_DecomposeSort(drawSurf->sort, &entityNum, &shader, &dlighted, &bStaticModel);
|
|
return SurfIsOffscreen((srfSurfaceFace_t*)drawSurf->surface, shader, entityNum);
|
|
}
|
|
|
|
/*
|
|
========================
|
|
R_MirrorViewBySurface
|
|
|
|
Returns qtrue if another view has been rendered
|
|
========================
|
|
*/
|
|
qboolean 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;
|
|
}
|
|
|
|
if ( r_noportals->integer || (r_fastsky->integer == 1) ) {
|
|
return qfalse;
|
|
}
|
|
|
|
// trivially reject portal/mirror
|
|
if (DrawSurfIsOffscreen( 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.ori.origin, &surface, &camera, newParms.ori.origin );
|
|
|
|
VectorSubtract( vec3_origin, camera.axis[0], newParms.portalPlane.normal );
|
|
newParms.portalPlane.dist = DotProduct( camera.origin, newParms.portalPlane.normal );
|
|
|
|
R_MirrorVector (oldParms.ori.axis[0], &surface, &camera, newParms.ori.axis[0]);
|
|
R_MirrorVector (oldParms.ori.axis[1], &surface, &camera, newParms.ori.axis[1]);
|
|
R_MirrorVector (oldParms.ori.axis[2], &surface, &camera, newParms.ori.axis[2]);
|
|
|
|
// OPTIMIZE: restrict the viewport on the mirrored view
|
|
|
|
// render the mirror view
|
|
R_RenderView (&newParms);
|
|
|
|
tr.viewParms = oldParms;
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
/*
|
|
==========================================================================================
|
|
|
|
DRAWSURF SORTING
|
|
|
|
==========================================================================================
|
|
*/
|
|
|
|
/*
|
|
===============
|
|
R_Radix
|
|
===============
|
|
*/
|
|
static ID_INLINE void R_Radix( int byte, 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 ) + byte;
|
|
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 ) + byte;
|
|
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
|
|
}
|
|
|
|
//==========================================================================================
|
|
|
|
/*
|
|
=================
|
|
R_AddDrawSurf
|
|
=================
|
|
*/
|
|
void R_AddDrawSurf(surfaceType_t* surface, shader_t* shader, int dlightMap) {
|
|
int index;
|
|
|
|
// instead of checking for overflow, we just mask the index
|
|
// so it wraps around
|
|
index = tr.refdef.numDrawSurfs & DRAWSURF_MASK;
|
|
// the sort data is packed into a single 32 bit value so it can be
|
|
// compared quickly during the qsorting process
|
|
tr.refdef.drawSurfs[index].sort = (shader->sortedIndex << QSORT_SHADERNUM_SHIFT)
|
|
| tr.shiftedEntityNum | tr.shiftedIsStatic | (dlightMap & 15);
|
|
tr.refdef.drawSurfs[index].surface = surface;
|
|
tr.refdef.numDrawSurfs++;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_AddSpriteSurf
|
|
=================
|
|
*/
|
|
void R_AddSpriteSurf(surfaceType_t* surface, shader_t* shader, float zDistance)
|
|
{
|
|
int index;
|
|
|
|
if (zDistance > MAX_SPRITE_DIST_SQUARED) {
|
|
zDistance = MAX_SPRITE_DIST_SQUARED;
|
|
}
|
|
|
|
index = tr.refdef.numSpriteSurfs % MAX_SPRITES;
|
|
tr.refdef.spriteSurfs[index].sort = (int)(MAX_SPRITE_DIST_SQUARED - zDistance) | (shader->sortedIndex << QSORT_SHADERNUM_SHIFT);
|
|
tr.refdef.spriteSurfs[index].surface = surface;
|
|
tr.refdef.numSpriteSurfs++;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_DecomposeSort
|
|
=================
|
|
*/
|
|
void R_DecomposeSort(unsigned int sort, int* entityNum, shader_t** shader, int* dlightMap, qboolean* bStaticModel) {
|
|
*shader = tr.sortedShaders[ ( sort >> QSORT_SHADERNUM_SHIFT ) & (MAX_SHADERS-1) ];
|
|
*entityNum = ( sort >> QSORT_ENTITYNUM_SHIFT ) & 4095;
|
|
*dlightMap = sort & 15;
|
|
*bStaticModel = sort & (1 << QSORT_STATICMODEL_SHIFT);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_SortDrawSurfs
|
|
=================
|
|
*/
|
|
void R_SortDrawSurfs( drawSurf_t *drawSurfs, int numDrawSurfs, drawSurf_t *spriteSurfs, int numSpriteSurfs ) {
|
|
shader_t *shader;
|
|
int entityNum;
|
|
int dlighted;
|
|
int i;
|
|
qboolean bStaticModel;
|
|
|
|
// 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, numDrawSurfs );
|
|
return;
|
|
}
|
|
|
|
// if we overflowed MAX_DRAWSURFS, the drawsurfs
|
|
// wrapped around in the buffer and we will be missing
|
|
// the first surfaces, not the last ones
|
|
if (numDrawSurfs > MAX_DRAWSURFS) {
|
|
numDrawSurfs = MAX_DRAWSURFS;
|
|
}
|
|
if (numSpriteSurfs > MAX_SPRITESURFS) {
|
|
numSpriteSurfs = MAX_SPRITESURFS;
|
|
}
|
|
|
|
// sort the drawsurfs by sort type, then orientation, then shader
|
|
R_RadixSort (drawSurfs, numDrawSurfs );
|
|
R_RadixSort (spriteSurfs, numSpriteSurfs );
|
|
|
|
R_Sky_Render();
|
|
|
|
if (!tr.viewParms.isPortal)
|
|
{
|
|
// check for any pass through drawing, which
|
|
// may cause another view to be rendered first
|
|
for (i = 0; i < numDrawSurfs; i++) {
|
|
R_DecomposeSort((drawSurfs + i)->sort, &entityNum, &shader, &dlighted, &bStaticModel);
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
R_AddDrawSurfCmd(drawSurfs, numDrawSurfs);
|
|
R_AddSpriteSurfCmd(spriteSurfs, numSpriteSurfs);
|
|
}
|
|
|
|
/*
|
|
=============
|
|
R_AddEntitySurfaces
|
|
=============
|
|
*/
|
|
void R_AddEntitySurfaces (void) {
|
|
trRefEntity_t *ent;
|
|
shader_t *shader;
|
|
|
|
tr.shiftedIsStatic = 0;
|
|
|
|
for ( tr.currentEntityNum = 0;
|
|
tr.currentEntityNum < tr.refdef.num_entities;
|
|
tr.currentEntityNum++ ) {
|
|
ent = tr.currentEntity = &tr.refdef.entities[tr.currentEntityNum];
|
|
|
|
ent->needDlights = qfalse;
|
|
|
|
// preshift the value we are going to OR into the drawsurf sort
|
|
tr.shiftedEntityNum = tr.currentEntityNum << QSORT_ENTITYNUM_SHIFT;
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
if (tr.viewParms.isPortalSky) {
|
|
// Expecting a sky entity in a sky portal
|
|
if (!(ent->e.renderfx & RF_SKYENTITY)) {
|
|
continue;
|
|
}
|
|
} else {
|
|
if (ent->e.renderfx & RF_SKYENTITY) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (tr.viewParms.isPortal) {
|
|
if (!(ent->e.renderfx & (RF_WRAP_FRAMES | RF_SHADOW_PLANE))) {
|
|
continue;
|
|
}
|
|
} else {
|
|
if (ent->e.renderfx & RF_SHADOW_PLANE) {
|
|
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_BEAM:
|
|
// 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, 0 );
|
|
break;
|
|
|
|
case RT_MODEL:
|
|
// we must set up parts of tr.ori for model culling
|
|
R_RotateForEntity( ent, &tr.viewParms, &tr.ori );
|
|
|
|
tr.currentModel = R_GetModelByHandle( ent->e.hModel );
|
|
if (!tr.currentModel) {
|
|
R_AddDrawSurf( &entitySurface, tr.defaultShader, 0 );
|
|
} else {
|
|
switch ( tr.currentModel->type ) {
|
|
case MOD_SPRITE:
|
|
ri.Printf(PRINT_ALL, "sprite model '%s' being added to renderer!\n", tr.currentModel->name);
|
|
break;
|
|
case MOD_TIKI:
|
|
R_AddSkelSurfaces( ent );
|
|
break;
|
|
case MOD_BRUSH:
|
|
R_AddBrushModelSurfaces( ent );
|
|
break;
|
|
case MOD_BAD: // null model axis
|
|
if ( (ent->e.renderfx & RF_THIRD_PERSON) && !tr.viewParms.isPortal) {
|
|
break;
|
|
}
|
|
shader = R_GetShaderByHandle( ent->e.customShader );
|
|
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" );
|
|
}
|
|
}
|
|
}
|
|
|
|
void R_AddSpriteSurfaces()
|
|
{
|
|
refSprite_t* sprite;
|
|
vec3_t delta;
|
|
|
|
if (!r_drawsprites->integer) {
|
|
return;
|
|
}
|
|
|
|
for (tr.currentSpriteNum = 0; tr.currentSpriteNum < tr.refdef.num_sprites; ++tr.currentSpriteNum)
|
|
{
|
|
sprite = &tr.refdef.sprites[tr.currentSpriteNum];
|
|
|
|
if (tr.viewParms.isPortalSky) {
|
|
if (!(sprite->renderfx & RF_SKYENTITY)) {
|
|
continue;
|
|
}
|
|
} else {
|
|
if (sprite->renderfx & RF_SKYENTITY) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (tr.viewParms.isPortal) {
|
|
if (!(sprite->renderfx & (RF_SHADOW_PLANE | RF_WRAP_FRAMES))) {
|
|
continue;
|
|
}
|
|
} else {
|
|
if (sprite->renderfx & RF_SHADOW_PLANE) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
tr.currentEntityNum = ENTITYNUM_WORLD;
|
|
tr.shiftedEntityNum = tr.currentEntityNum << QSORT_ENTITYNUM_SHIFT;
|
|
if (sprite->hModel && sprite->hModel < tr.numModels) {
|
|
tr.currentModel = &tr.models[sprite->hModel];
|
|
} else {
|
|
tr.currentModel = &tr.models[0];
|
|
}
|
|
|
|
R_AdjustVisBoundsForSprite(&tr.refdef.sprites[tr.currentSpriteNum], &tr.viewParms, &tr.ori );
|
|
sprite->shaderNum = tr.currentModel->d.sprite->shader->sortedIndex;
|
|
|
|
VectorSubtract(sprite->origin, tr.refdef.vieworg, delta);
|
|
R_AddSpriteSurf(&sprite->surftype, tr.currentModel->d.sprite->shader, VectorLengthSquared(delta));
|
|
}
|
|
}
|
|
|
|
/*
|
|
====================
|
|
R_GenerateDrawSurfs
|
|
====================
|
|
*/
|
|
void R_GenerateDrawSurfs( void ) {
|
|
R_AddWorldSurfaces ();
|
|
if (!(tr.refdef.rdflags & RDF_NOWORLDMODEL)) {
|
|
R_AddSwipeSurfaces();
|
|
}
|
|
|
|
R_AddPolygonSurfaces();
|
|
|
|
R_AddTerrainMarkSurfaces();
|
|
|
|
R_AddEntitySurfaces();
|
|
|
|
R_AddSpriteSurfaces();
|
|
|
|
// 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_DebugPolygon
|
|
================
|
|
*/
|
|
void R_DebugPolygon( int color, int numPoints, float *points ) {
|
|
int i;
|
|
|
|
GL_State( GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE );
|
|
|
|
// draw solid shade
|
|
|
|
qglColor3f( color&1, (color>>1)&1, (color>>2)&1 );
|
|
qglBegin( GL_POLYGON );
|
|
for ( i = 0 ; i < numPoints ; i++ ) {
|
|
qglVertex3fv( points + i * 3 );
|
|
}
|
|
qglEnd();
|
|
|
|
// draw wireframe outline
|
|
GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE );
|
|
qglDepthRange( 0, 0 );
|
|
qglColor3f( 1, 1, 1 );
|
|
qglBegin( GL_POLYGON );
|
|
for ( i = 0 ; i < numPoints ; i++ ) {
|
|
qglVertex3fv( points + i * 3 );
|
|
}
|
|
qglEnd();
|
|
qglDepthRange( 0, 1 );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
R_DebugGraphics
|
|
|
|
Visualization aid for movement clipping debugging
|
|
====================
|
|
*/
|
|
void R_DebugGraphics( void ) {
|
|
if ( !r_debugSurface->integer ) {
|
|
return;
|
|
}
|
|
|
|
// the render thread can't make callbacks to the main thread
|
|
R_IssuePendingRenderCommands();
|
|
|
|
GL_Bind( tr.whiteImage);
|
|
GL_Cull( CT_FRONT_SIDED );
|
|
ri.CM_DrawDebugSurface( R_DebugPolygon );
|
|
}
|
|
|
|
#define CIRCLE_LENGTH 25
|
|
|
|
/*
|
|
================
|
|
R_DebugCircle
|
|
================
|
|
*/
|
|
void R_DebugCircle(const vec3_t org, float radius, float r, float g, float b, float alpha, qboolean horizontal) {
|
|
int i;
|
|
float ang;
|
|
debugline_t* line;
|
|
vec3_t forward, right;
|
|
vec3_t pos, lastpos;
|
|
|
|
if (!ri.DebugLines || !ri.numDebugLines) {
|
|
return;
|
|
}
|
|
|
|
if (horizontal)
|
|
{
|
|
VectorSet(forward, 1, 0, 0);
|
|
VectorSet(right, 0, 1, 0);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(tr.refdef.viewaxis[1], right);
|
|
VectorCopy(tr.refdef.viewaxis[2], forward);
|
|
}
|
|
|
|
VectorClear(pos);
|
|
VectorClear(lastpos);
|
|
|
|
for (i = 0; i < CIRCLE_LENGTH; i++) {
|
|
VectorCopy(pos, lastpos);
|
|
|
|
ang = DEG2RAD((float)(i * 15));
|
|
pos[0] = (org[0] + sin(ang) * radius * forward[0]) +
|
|
cos(ang) * radius * right[0];
|
|
pos[1] = (org[1] + sin(ang) * radius * forward[1]) +
|
|
cos(ang) * radius * right[1];
|
|
pos[2] = (org[2] + sin(ang) * radius * forward[2]) +
|
|
cos(ang) * radius * right[2];
|
|
|
|
if (i > 0)
|
|
{
|
|
if (*ri.numDebugLines >= r_numdebuglines->integer) {
|
|
ri.Printf(PRINT_ALL, "R_DebugCircle: Exceeded MAX_DEBUG_LINES\n");
|
|
return;
|
|
}
|
|
|
|
line = &(*ri.DebugLines)[*ri.numDebugLines];
|
|
(*ri.numDebugLines)++;
|
|
VectorCopy(lastpos, line->start);
|
|
VectorCopy(pos, line->end);
|
|
VectorSet(line->color, r, g, b);
|
|
line->alpha = alpha;
|
|
line->width = 1.0;
|
|
line->factor = 1;
|
|
line->pattern = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_DebugLine
|
|
================
|
|
*/
|
|
void R_DebugLine(const vec3_t start, const vec3_t end, float r, float g, float b, float alpha) {
|
|
debugline_t* line;
|
|
|
|
if (!ri.DebugLines || !ri.numDebugLines) {
|
|
return;
|
|
}
|
|
|
|
if (*ri.numDebugLines >= r_numdebuglines->integer) {
|
|
ri.Printf(PRINT_ALL, "R_DebugLine: Exceeded MAX_DEBUG_LINES\n");
|
|
return;
|
|
}
|
|
|
|
line = &(*ri.DebugLines)[*ri.numDebugLines];
|
|
(*ri.numDebugLines)++;
|
|
VectorCopy(start, line->start);
|
|
VectorCopy(end, line->end);
|
|
VectorSet(line->color, r, g, b);
|
|
line->alpha = alpha;
|
|
line->width = 1.0;
|
|
line->factor = 1;
|
|
line->pattern = -1;
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_DrawDebugLines
|
|
================
|
|
*/
|
|
void R_DrawDebugLines(void) {
|
|
debugline_t* line;
|
|
int i;
|
|
float width;
|
|
int factor;
|
|
unsigned short pattern;
|
|
|
|
if (!ri.DebugLines || !ri.numDebugLines) {
|
|
return;
|
|
}
|
|
|
|
if (!*ri.numDebugLines) {
|
|
return;
|
|
}
|
|
|
|
if (tr.refdef.rdflags & RDF_NOWORLDMODEL) {
|
|
return;
|
|
}
|
|
|
|
if (tr.viewParms.isPortalSky) {
|
|
return;
|
|
}
|
|
|
|
R_IssuePendingRenderCommands();
|
|
GL_Bind(tr.whiteImage);
|
|
if (r_debuglines_depthmask->integer) {
|
|
GL_State(GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE);
|
|
} else {
|
|
GL_State(GLS_POLYMODE_LINE);
|
|
}
|
|
|
|
qglDisableClientState(GL_COLOR_ARRAY);
|
|
qglDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
|
|
|
width = 1.0;
|
|
factor = 4;
|
|
pattern = -1;
|
|
|
|
if (r_stipplelines->integer)
|
|
{
|
|
qglEnable(GL_LINE_STIPPLE);
|
|
qglLineStipple(4, -1);
|
|
qglLineWidth(1.0f);
|
|
}
|
|
|
|
qglBegin(GL_LINES);
|
|
|
|
for (i = 0; i < *ri.numDebugLines; i++) {
|
|
line = &(*ri.DebugLines)[i];
|
|
|
|
if (r_stipplelines->integer) {
|
|
if (line->width != width || line->factor != factor || line->pattern != pattern) {
|
|
qglEnd();
|
|
qglLineStipple(line->factor, line->pattern);
|
|
qglLineWidth(line->width);
|
|
qglBegin(GL_LINES);
|
|
factor = line->factor;
|
|
width = line->width;
|
|
pattern = line->pattern;
|
|
}
|
|
}
|
|
|
|
qglColor4f(line->color[0], line->color[1], line->color[2], line->alpha);
|
|
qglVertex3fv(line->start);
|
|
qglVertex3fv(line->end);
|
|
}
|
|
|
|
qglEnd();
|
|
|
|
if (r_stipplelines->integer)
|
|
{
|
|
qglDisable(GL_LINE_STIPPLE);
|
|
qglLineStipple(1, -1);
|
|
qglLineWidth(1.0);
|
|
}
|
|
|
|
qglDepthRange(0.0, 1.0);
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_DrawDebugLines
|
|
================
|
|
*/
|
|
void R_DrawDebugStrings(void) {
|
|
int i;
|
|
debugstring_t* string;
|
|
|
|
if (!ri.DebugStrings || !ri.numDebugStrings) {
|
|
return;
|
|
}
|
|
|
|
if (!*ri.numDebugStrings) {
|
|
return;
|
|
}
|
|
|
|
if (tr.refdef.rdflags & RDF_NOWORLDMODEL) {
|
|
return;
|
|
}
|
|
|
|
if (tr.viewParms.isPortalSky) {
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < *ri.numDebugStrings; i++) {
|
|
string = &(*ri.DebugStrings)[i];
|
|
|
|
R_DrawFloatingString(
|
|
tr.pFontDebugStrings,
|
|
string->szText,
|
|
string->pos,
|
|
string->color,
|
|
string->scale,
|
|
64
|
|
);
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_RenderView
|
|
|
|
A view may be either the actual camera view,
|
|
or a mirror / remote location
|
|
================
|
|
*/
|
|
void R_RenderView (viewParms_t *parms) {
|
|
int firstDrawSurf;
|
|
int firstSpriteSurf;
|
|
|
|
if ( parms->viewportWidth <= 0 || parms->viewportHeight <= 0 ) {
|
|
return;
|
|
}
|
|
|
|
tr.viewCount++;
|
|
|
|
tr.viewParms = *parms;
|
|
tr.viewParms.frameSceneNum = tr.frameSceneNum;
|
|
tr.viewParms.frameCount = tr.frameCount;
|
|
|
|
firstDrawSurf = tr.refdef.numDrawSurfs;
|
|
firstSpriteSurf = tr.refdef.numSpriteSurfs;
|
|
|
|
tr.viewCount++;
|
|
|
|
// set viewParms.world
|
|
R_RotateForViewer ();
|
|
|
|
R_SetupFrustum ();
|
|
|
|
R_Sky_Reset();
|
|
|
|
R_DrawDebugStrings();
|
|
|
|
R_GenerateDrawSurfs();
|
|
|
|
R_SortDrawSurfs(
|
|
tr.refdef.drawSurfs + firstDrawSurf, tr.refdef.numDrawSurfs - firstDrawSurf,
|
|
tr.refdef.spriteSurfs + firstSpriteSurf, tr.refdef.numSpriteSurfs - firstSpriteSurf
|
|
);
|
|
|
|
R_DrawDebugLines();
|
|
R_DebugSkeleton();
|
|
|
|
// draw main system development information (surface outlines, etc)
|
|
R_DebugGraphics();
|
|
}
|
|
|
|
|
|
|