/*
===========================================================================
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();
}



