//-----------------------------------------------------------------------------
//
//  $Logfile:: /Source/r_main.c                                               $
// $Revision:: 5                                                              $
//   $Author:: Burgermac                                                      $
//     $Date:: 8/21/01 6:07 PM                                                $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// DESCRIPTION:
//

#include "r_local.h"
#include "client.h"
#include "r_sinmdl.h"

#ifdef __A3D_GEOM
void A3D_RenderGeometry (void *pA3D, void *pGeom, void *pMat, void *pGeomStatus);
#endif

refexport_t GetRefAPI (refimport_t rimp);

rviddef_t vid;
refimport_t	ri;

byte d_8to24table[1024];

entity_t	r_worldentity;

char		skyname[MAX_QPATH];
float		skyrotate;
vec3_t		skyaxis;
image_t		*sky_images[6];

refdef_t	r_newrefdef;
model_t		*currentmodel;

model_t		*r_worldmodel;

byte		r_warpbuffer[WARP_WIDTH * WARP_HEIGHT];

swstate_t sw_state;

void		*colormap;
vec3_t		viewlightvec;
alight_t	r_viewlighting = {128, 192, viewlightvec};
float		r_time1;
int			r_numallocatededges;
float		r_aliasuvscale = 1.0;
int			r_outofsurfaces;
int			r_outofedges;

qboolean	r_dowarp;

mvertex_t	*r_pcurrentvertbase;

int			c_surf;
int			r_maxsurfsseen, r_maxedgesseen, r_cnumsurfs;
qboolean	r_surfsonstack;
int			r_clipflags;

//
// view origin
//
vec3_t	vup, base_vup;
vec3_t	vpn, base_vpn;
vec3_t	vright, base_vright;
vec3_t	r_origin;

//
// screen size info
//
oldrefdef_t	r_refdef;
float		xcenter, ycenter;
float		xscale, yscale;
float		xscaleinv, yscaleinv;
float		xscaleshrink, yscaleshrink;
float		aliasxscale, aliasyscale, aliasxcenter, aliasycenter;

int		r_screenwidth;

float	verticalFieldOfView;
float	xOrigin, yOrigin;

mplane_t	screenedge[4];

//
// refresh flags
//
int		r_framecount = 1;	// so frame counts initialized to 0 don't match
int		r_visframecount;
int		d_spanpixcount;
int		r_polycount;
int		r_drawnpolycount;
int		r_wholepolycount;

int			*pfrustum_indexes[4];
int			r_frustum_indexes[4*6];

mleaf_t		*r_viewleaf;
int			r_viewcluster, r_oldviewcluster;

image_t  	*r_notexture_mip;

float	da_time1, da_time2, dp_time1, dp_time2, db_time1, db_time2, rw_time1, rw_time2;
float	se_time1, se_time2, de_time1, de_time2;

void R_MarkLeaves (void);

cvar_t	*r_lefthand;
cvar_t	*sw_aliasstats;
#ifdef _WIN32
cvar_t	*sw_allow_modex;
#endif
cvar_t	*sw_clearcolor;
cvar_t	*sw_drawflat;
cvar_t	*sw_draworder;
cvar_t	*sw_maxedges;
cvar_t	*sw_maxsurfs;
cvar_t  *sw_mode;
cvar_t	*sw_reportedgeout;
cvar_t	*sw_reportsurfout;
cvar_t  *sw_stipplealpha;
cvar_t	*sw_surfcacheoverride;
cvar_t	*sw_waterwarp;

cvar_t	*r_drawworld;
cvar_t	*r_drawentities;
cvar_t	*r_dspeeds;
cvar_t	*r_fullbright;
cvar_t  *r_lerpmodels;
cvar_t  *r_novis;

cvar_t	*r_speeds;
cvar_t	*r_lightlevel;	//FIXME HACK

int		cacheheight;
image_t	*r_envmap;
image_t	*r_goldenvmap;
// 2015 code 
image_t	*r_gogglesmap;
cvar_t	*r_condist;
cvar_t	*r_conthresh;
cvar_t	*r_conoffset;
cvar_t	*r_conchars;
cvar_t	*r_detaildist;
cvar_t	*r_8bpp;
cvar_t	*r_colorlighting;
cvar_t	*r_monolighting;
cvar_t	*r_dynamic;

// 2015 code 
#ifndef REF_HARD_LINKED
cvar_t	*game2015;
#endif

#ifndef REF_HARD_LINKED
cvar_t	*vid_gamma;
cvar_t	*scr_shotname;
cvar_t	*vid_fullscreen;
#endif


#if	!id386

// r_vars.c

// all global and static refresh variables are collected in a contiguous block
// to avoid cache conflicts.

//-------------------------------------------------------
// global refresh variables
//-------------------------------------------------------

// FIXME: make into one big structure, like cl or sv
// FIXME: do separately for refresh engine and driver


// d_vars.c

// all global and static refresh variables are collected in a contiguous block
// to avoid cache conflicts.

//-------------------------------------------------------
// global refresh variables
//-------------------------------------------------------

// FIXME: make into one big structure, like cl or sv
// FIXME: do separately for refresh engine and driver

float	d_sdivzstepu, d_tdivzstepu, d_zistepu;
float	d_sdivzstepv, d_tdivzstepv, d_zistepv;
float	d_sdivzorigin, d_tdivzorigin, d_ziorigin;

fixed16_t	sadjust, tadjust, bbextents, bbextentt;

pixel_t			*cacheblock;
int				cachewidth;
pixel_t			*d_viewbuffer;
short			*d_pzbuffer;
unsigned int	d_zrowbytes;
unsigned int	d_zwidth;

#endif	/* !id386 */

byte	r_notexture_buffer[1024];


/*
==================
R_InitTextures
==================
*/
static void	R_InitTextures (void)
{
	int		x,y, m;
	byte	*dest;

// create a simple checkerboard texture for the default
	r_notexture_mip = (image_t *)&r_notexture_buffer;

	r_notexture_mip->width = r_notexture_mip->height = 16;
	r_notexture_mip->pixels[0] = &r_notexture_buffer[sizeof(image_t)];
	r_notexture_mip->pixels[1] = r_notexture_mip->pixels[0] + 16*16;
	r_notexture_mip->pixels[2] = r_notexture_mip->pixels[1] + 8*8;
	r_notexture_mip->pixels[3] = r_notexture_mip->pixels[2] + 4*4;

	for (m=0 ; m<4 ; m++)
	{
		dest = r_notexture_mip->pixels[m];
		for (y=0 ; y< (16>>m) ; y++)
			for (x=0 ; x< (16>>m) ; x++)
			{
				if (  (y< (8>>m) ) ^ (x< (8>>m) ) )

					*dest++ = 0;
				else
					*dest++ = 0xff;
			}
	}
}


/*
================
R_InitTurb
================
*/
void R_InitTurb (void)
{
	int		i;

	for (i = 0; i < SIN_BUFFER_SIZE; i++)
	{
		sintable[i] = AMP + sin(i*3.14159*2/CYCLE)*AMP;
		intsintable[i] = AMP2 + sin(i*3.14159*2/CYCLE)*AMP2;	// AMP2, not 20
	}
}

void R_ImageList_f( void );

static void R_Register (void)
{
	sw_aliasstats = ri.Cvar_Get ("sw_polymodelstats", "0", 0);
#ifdef _WIN32
	sw_allow_modex = ri.Cvar_Get( "sw_allow_modex", "1", CVAR_ARCHIVE );
#endif
	sw_clearcolor = ri.Cvar_Get ("sw_clearcolor", "2", 0);
	sw_drawflat = ri.Cvar_Get ("r_drawflat", "0", 0);
	sw_draworder = ri.Cvar_Get ("r_draworder", "0", 0);
	sw_maxedges = ri.Cvar_Get ("sw_maxedges", "3400", 0);
	sw_maxsurfs = ri.Cvar_Get ("sw_maxsurfs", "1300", 0);
	sw_mipcap = ri.Cvar_Get ("sw_mipcap", "0", 0);
	sw_mipscale = ri.Cvar_Get ("sw_mipscale", "1", 0);
	sw_reportedgeout = ri.Cvar_Get ("sw_reportedgeout", "0", 0);
	sw_reportsurfout = ri.Cvar_Get ("sw_reportsurfout", "0", 0);
	sw_stipplealpha = ri.Cvar_Get( "sw_stipplealpha", "0", CVAR_ARCHIVE );
	sw_surfcacheoverride = ri.Cvar_Get ("sw_surfcacheoverride", "0", 0);
	sw_waterwarp = ri.Cvar_Get ("sw_waterwarp", "1", 0);
	sw_mode = ri.Cvar_Get( "sw_mode", "0", CVAR_ARCHIVE );

	r_lefthand = ri.Cvar_Get( "hand", "0", CVAR_USERINFO | CVAR_ARCHIVE );
	r_speeds = ri.Cvar_Get ("r_speeds", "0", 0);
	r_fullbright = ri.Cvar_Get ("r_fullbright", "0", 0);
	r_drawentities = ri.Cvar_Get ("r_drawentities", "1", 0);
	r_drawworld = ri.Cvar_Get ("r_drawworld", "1", 0);
	r_dspeeds = ri.Cvar_Get ("r_dspeeds", "0", 0);
	r_lightlevel = ri.Cvar_Get ("r_lightlevel", "0", 0);
	r_lerpmodels = ri.Cvar_Get( "r_lerpmodels", "1", 0 );
	r_novis = ri.Cvar_Get( "r_novis", "0", 0 );

	vid_fullscreen = ri.Cvar_Get( "vid_fullscreen", "1", CVAR_ARCHIVE );
	vid_gamma = ri.Cvar_Get( "vid_gamma", "1.0", CVAR_ARCHIVE );

	scr_shotname = ri.Cvar_Get( "scr_shotname", SCREEN_SHOT_NAME, CVAR_ARCHIVE );
	r_condist    = ri.Cvar_Get ("r_condist", "100000", CVAR_ARCHIVE );
	r_conoffset  = ri.Cvar_Get ("r_conoffset", "0.1", CVAR_ARCHIVE);
	r_conthresh  = ri.Cvar_Get ("r_conthresh", "100000", CVAR_ARCHIVE);
	r_conchars   = ri.Cvar_Get ("r_conchars", "pics/conchars.tga", 0);
	r_detaildist = ri.Cvar_Get ("r_detaildist", "300000", CVAR_ARCHIVE );
#ifdef __DJGPP__
	r_8bpp = ri.Cvar_Get( "r_8bpp", "1", CVAR_ARCHIVE ); /* FS: Assume Pentium 2 */
#else
	r_8bpp = ri.Cvar_Get( "r_8bpp", "0", CVAR_ARCHIVE );
#endif
	ri.Cvar_SetDescription("r_8bpp", "Set to 0 to use 16bpp colour modes in software renderer.");
	r_colorlighting = ri.Cvar_Get( "r_colorlighting", "0", CVAR_ARCHIVE );
	ri.Cvar_SetDescription("r_colorlighting", "Allow coloured lighting in software renderer.  Recommended to be disabled in 8bpp modes.  Always enforced on in 16bpp modes.");
	r_monolighting = ri.Cvar_Get( "r_monolighting", "0", CVAR_ARCHIVE );
	r_dynamic = ri.Cvar_Get ("r_dynamic", "1", CVAR_ARCHIVE);
	ri.Cmd_AddCommand( "initfont", Draw_InitLocal );
	ri.Cmd_AddCommand( "loadpal", R_LoadPalFile_f );

	// 2015 code 
	game2015 = ri.Cvar_Get ("game2015", "0", 0);

	ri.Cmd_AddCommand ("modellist", Mod_Modellist_f);
	ri.Cmd_AddCommand( "screenshot", R_ScreenShot_f );
	ri.Cmd_AddCommand( "imagelist", R_ImageList_f );

	sw_mode->modified = true; // force us to do mode specific stuff later
	vid_gamma->modified = true; // force us to rebuild the gamma table later
}

void R_UnRegister (void)
{
	ri.Cmd_RemoveCommand( "initfont" );
	ri.Cmd_RemoveCommand( "loadpal" );
	ri.Cmd_RemoveCommand( "screenshot" );
	ri.Cmd_RemoveCommand( "modellist" );
	ri.Cmd_RemoveCommand( "imagelist" );
}

/*
===============
R_Init
===============
*/
int R_Init( void *hInstance, void *wndProc )
{
	R_InitImages ();
	Mod_Init ();
	R_InitTextures ();

	R_InitTurb ();

	view_clipplanes[0].leftedge = true;
	view_clipplanes[1].rightedge = true;
	view_clipplanes[1].leftedge = view_clipplanes[2].leftedge =
			view_clipplanes[3].leftedge = false;
	view_clipplanes[0].rightedge = view_clipplanes[2].rightedge =
			view_clipplanes[3].rightedge = false;

	r_refdef.xOrigin = XCENTERING;
	r_refdef.yOrigin = YCENTERING;

// TODO: collect 386-specific code in one place
#if	id386
	Sys_MakeCodeWriteable ((long)R_EdgeCodeStart,
					     (long)R_EdgeCodeEnd - (long)R_EdgeCodeStart);
	Sys_SetFPCW ();		// get bit masks for FPCW	(FIXME: is this id386?)
#endif	// id386

	r_aliasuvscale = 1.0;

	R_Register ();

	if (!SWimp_Init(hInstance, wndProc))
		return -1;

	// create the window
	R_BeginFrame( 0 );

	Draw_InitLocal ();

	ri.Con_Printf (PRINT_ALL, "ref_soft version: " REF_VERSION "\n");

	return 0;
}

/*
===============
R_Shutdown
===============
*/
void R_Shutdown (void)
{
	// free z buffer
	free(d_pzbuffer);
	d_pzbuffer = NULL;

	// free surface cache
	if (sc_base) {
		D_FlushCaches ();
		free (sc_base);
		sc_base = NULL;
	}

	// free colormap
	free(vid.colormap);
	vid.colormap = NULL;

	// free alphamap16
	free(vid.alphamap16);
	vid.alphamap16 = NULL;

	// free redlightmap16
	free(vid.redlightmap16);
	vid.redlightmap16 = NULL;

	// free greenlightmap16
	free(vid.greenlightmap16);
	vid.greenlightmap16 = NULL;

	// free bluelightmap16
	free(vid.bluelightmap16);
	vid.bluelightmap16 = NULL;

	// free blitmap
	free(vid.blitmap);
	vid.blitmap = NULL;

	// free unblitmap
	free(vid.unblitmap);
	vid.unblitmap = NULL;

	// free color_16to8table
	free(vid.color_16to8table);
	vid.color_16to8table = NULL;

	free(d_16to8table);
	d_16to8table = 0;

	R_UnRegister ();
	Mod_FreeAll ();
	R_ShutdownImages ();

	SWimp_Shutdown();
}

/*
===============
R_NewMap
===============
*/
void R_NewMap (void)
{
	r_viewcluster = -1;

	r_cnumsurfs = sw_maxsurfs->value;

	if (r_cnumsurfs <= MINSURFACES)
		r_cnumsurfs = MINSURFACES;

	if (r_cnumsurfs > NUMSTACKSURFACES)
	{
		surfaces = (surf_t *)malloc (r_cnumsurfs * sizeof(surf_t));
		surface_p = surfaces;
		surf_max = &surfaces[r_cnumsurfs];
		r_surfsonstack = false;
	// surface 0 doesn't really exist; it's just a dummy because index 0
	// is used to indicate no edge attached to surface
		surfaces--;
		R_SurfacePatch ();
	}
	else
	{
		r_surfsonstack = true;
	}

	r_maxedgesseen = 0;
	r_maxsurfsseen = 0;

	r_numallocatededges = sw_maxedges->value;

	if (r_numallocatededges < MINEDGES)
		r_numallocatededges = MINEDGES;

	if (r_numallocatededges <= NUMSTACKEDGES)
	{
		auxedges = NULL;
	}
	else
	{
		auxedges = (edge_t *)malloc (r_numallocatededges * sizeof(edge_t));
	}
}


/*
===============
R_MarkLeaves

Mark the leaves and nodes that are in the PVS for the current
cluster
===============
*/
void R_MarkLeaves (void)
{
	byte	*vis;
	mnode_t	*node;
	int		i;
	mleaf_t	*leaf;
	int		cluster;

	if (r_oldviewcluster == r_viewcluster && !r_novis->value && r_viewcluster != -1)
		return;

	r_visframecount++;
	r_oldviewcluster = r_viewcluster;

	if (r_novis->value || r_viewcluster == -1 || !r_worldmodel->vis)
	{
		// mark everything
		for (i=0 ; i<r_worldmodel->numleafs ; i++)
			r_worldmodel->leafs[i].visframe = r_visframecount;
		for (i=0 ; i<r_worldmodel->numnodes ; i++)
			r_worldmodel->nodes[i].visframe = r_visframecount;
		return;
	}

	vis = Mod_ClusterPVS (r_viewcluster, r_worldmodel);

	for (i=0,leaf=r_worldmodel->leafs ; i<r_worldmodel->numleafs ; i++, leaf++)
	{
		cluster = leaf->cluster;
		if (cluster == -1)
			continue;
		if (vis[cluster>>3] & (1<<(cluster&7)))
		{
			node = (mnode_t *)leaf;
			do
			{
				if (node->visframe == r_visframecount)
					break;
				node->visframe = r_visframecount;
				node = node->parent;
			} while (node);
		}
	}

#if 0
	for (i=0 ; i<r_worldmodel->vis->numclusters ; i++)
	{
		if (vis[i>>3] & (1<<(i&7)))
		{
			node = (mnode_t *)&r_worldmodel->leafs[i];	// FIXME: cluster
			do
			{
				if (node->visframe == r_visframecount)
					break;
				node->visframe = r_visframecount;
				node = node->parent;
			} while (node);
		}
	}
#endif
}

/*
** R_DrawNullModel
**
** IMPLEMENT THIS!
*/
static void R_DrawNullModel( void )
{
}

/*
=============
R_DrawEntitiesOnList
=============
*/
static void R_DrawEntitiesOnList (void)
{
	int			i;
	qboolean	translucent_entities = false;

	if (!r_drawentities->value || (r_newrefdef.rdflags & RDF_NOENTITIES))
		return;

	// all bmodels have already been drawn by the edge list
	for (i=0 ; i<r_newrefdef.num_entities ; i++)
	{
		currententity = &r_newrefdef.entities[i];

		if (currententity->flags & RF_DONTDRAW)
			continue;
		if ( currententity->flags & RF_DETAIL )
		{
			float dist;
			vec3_t diff;

			VectorSubtract( currententity->origin, r_newrefdef.vieworg, diff );

			dist = DotProduct( diff,diff );
			dist *= r_newrefdef.fovfactor;

			if ( dist > r_detaildist->value )
			{
				continue;
			}
		}

		if ( currententity->flags & RF_TRANSLUCENT )
		{
			translucent_entities = true;
			continue;
		}

		if ( currententity->flags & RF_BEAM )
		{
			modelorg[0] = -r_origin[0];
			modelorg[1] = -r_origin[1];
			modelorg[2] = -r_origin[2];
			VectorCopy( vec3_origin, r_entorigin );
			R_DrawBeam( currententity );
		}
		else
		{
			currentmodel = currententity->model;
			if (!currentmodel)
			{
				R_DrawNullModel();
				continue;
			}
			VectorCopy (currententity->origin, r_entorigin);
			VectorSubtract (r_origin, r_entorigin, modelorg);

			switch (currentmodel->type)
			{
			case mod_sprite:
				R_DrawSprite ();
				break;

			case mod_alias:
				R_AliasDrawModel ();
				break;

			case mod_sinmdl:
				R_SINMDLDrawModel ();
				break;

			default:
				break;
			}
		}
	}

	if ( !translucent_entities )
		return;

	for (i=0 ; i<r_newrefdef.num_entities ; i++)
	{
		currententity = &r_newrefdef.entities[i];

		if (currententity->flags & RF_DONTDRAW)
			continue;
		if ( currententity->flags & RF_DETAIL )
		{
			float dist;
			vec3_t diff;

			VectorSubtract( currententity->origin, r_newrefdef.vieworg, diff );

			dist = DotProduct( diff,diff );
			dist  *= r_newrefdef.fovfactor;

			if ( dist > r_detaildist->value )
			{
				continue;
			}
		}

		if ( !( currententity->flags & RF_TRANSLUCENT ) )
			continue;

		if ( currententity->flags & RF_BEAM )
		{
			modelorg[0] = -r_origin[0];
			modelorg[1] = -r_origin[1];
			modelorg[2] = -r_origin[2];
			VectorCopy( vec3_origin, r_entorigin );
			R_DrawBeam( currententity );
		}
		else
		{
			currentmodel = currententity->model;
			if (!currentmodel)
			{
				R_DrawNullModel();
				continue;
			}
			VectorCopy (currententity->origin, r_entorigin);
			VectorSubtract (r_origin, r_entorigin, modelorg);

			switch (currentmodel->type)
			{
			case mod_sprite:
				R_DrawSprite ();
				break;

			case mod_alias:
				R_AliasDrawModel ();
				break;

			case mod_sinmdl:
				R_SINMDLDrawModel ();
				break;

			default:
				break;
			}
		}
	}
}


/*
=============
R_BmodelCheckBBox
=============
*/
static int R_BmodelCheckBBox (float *minmaxs)
{
	int			i, *pindex, clipflags;
	vec3_t		acceptpt, rejectpt;
	float		d;

	clipflags = 0;

	for (i=0 ; i<4 ; i++)
	{
	// generate accept and reject points
	// FIXME: do with fast look-ups or integer tests based on the sign bit
	// of the floating point values

		pindex = pfrustum_indexes[i];

		rejectpt[0] = minmaxs[pindex[0]];
		rejectpt[1] = minmaxs[pindex[1]];
		rejectpt[2] = minmaxs[pindex[2]];

		d = DotProduct (rejectpt, view_clipplanes[i].normal);
		d -= view_clipplanes[i].dist;

		if (d <= 0)
			return BMODEL_FULLY_CLIPPED;

		acceptpt[0] = minmaxs[pindex[3+0]];
		acceptpt[1] = minmaxs[pindex[3+1]];
		acceptpt[2] = minmaxs[pindex[3+2]];

		d = DotProduct (acceptpt, view_clipplanes[i].normal);
		d -= view_clipplanes[i].dist;

		if (d <= 0)
			clipflags |= (1<<i);
	}

	return clipflags;
}


/*
===================
R_FindTopnode

Find the first node that splits the given box
===================
*/
static mnode_t *R_FindTopnode (vec3_t mins, vec3_t maxs)
{
	mplane_t	*splitplane;
	int			sides;
	mnode_t *node;

	node = r_worldmodel->nodes;

	while (1)
	{
		if (node->visframe != r_visframecount)
			return NULL;		// not visible at all

		if (node->contents != CONTENTS_NODE)
		{
			if (node->contents != CONTENTS_SOLID)
				return	node; // we've reached a non-solid leaf, so it's
							//  visible and not BSP clipped
			return NULL;	// in solid, so not visible
		}

		splitplane = node->plane;
      sides = BOX_ON_PLANE_SIDE(mins, maxs, (cplane_t *)splitplane);

		if (sides == 3)
			return node;	// this is the splitter

	// not split yet; recurse down the contacted side
		if (sides & 1)
			node = node->children[0];
		else
			node = node->children[1];
	}
}


/*
=============
RotatedBBox

Returns an axially aligned box that contains the input box at the given rotation
=============
*/

static void RotatedBBox (vec3_t mins, vec3_t maxs, entity_t *ent, vec3_t tmins, vec3_t tmaxs)
{
	vec3_t tmp, v;
	int i, j;

	if (!ent->angles[0] && !ent->angles[1] && !ent->angles[2])
	{
		//ri.Sys_Error (ERR_FATAL,"Just checking if it worked");
		VectorCopy ( mins, tmins );
		VectorCopy ( maxs, tmaxs );
		return;
	}

	for (i=0; i<3; i++)
	{
		tmins[i] = 99999;
		tmaxs[i] = -99999;
	}

	for ( i = 0; i < 8; i++ )
	{
		if ( i & 1 )
			tmp[0] = mins[0];
		else
			tmp[0] = maxs[0];

		if ( i & 2 )
			tmp[1] = mins[1];
		else
			tmp[1] = maxs[1];

		if ( i & 4 )
			tmp[2] = mins[2];
		else
			tmp[2] = maxs[2];


		VectorScale ( ent->mat[0], tmp[0], v );
		VectorMA ( v, tmp[1], ent->mat[1], v );
		VectorMA ( v, tmp[2], ent->mat[2], v );

		for (j=0; j<3; j++)
		{
			if (v[j] < tmins[j])
				tmins[j] = v[j];
			if (v[j] > tmaxs[j])
				tmaxs[j] = v[j];
		}
	}
}

/*
=============
R_DrawBEntitiesOnList
=============
*/
static void R_DrawBEntitiesOnList (void)
{
	int			i, clipflags;
	vec3_t		oldorigin;
	vec3_t		mins, maxs;
	float		minmaxs[6];
	mnode_t		*topnode;

	if (!r_drawentities->value || (r_newrefdef.rdflags & RDF_NOENTITIES))
		return;

	VectorCopy (modelorg, oldorigin);
	insubmodel = true;
	r_dlightframecount = r_framecount;

	for (i=0 ; i<r_newrefdef.num_entities ; i++)
	{
		currententity = &r_newrefdef.entities[i];
		currentmodel = currententity->model;
		if (!currentmodel)
			continue;
		if (currentmodel->nummodelsurfaces == 0)
			continue;	// clip brush only
		if (currententity->flags & (RF_DONTDRAW|RF_BEAM))
			continue;
		if (currentmodel->type != mod_brush)
			continue;

	// see if the bounding box lets us trivially reject, also sets
	// trivial accept status
		RotatedBBox (currentmodel->mins, currentmodel->maxs,
			currententity, mins, maxs);

		VectorAdd (mins, currententity->origin, minmaxs);
		VectorAdd (maxs, currententity->origin, (minmaxs+3));

		clipflags = R_BmodelCheckBBox (minmaxs);
		if (clipflags == BMODEL_FULLY_CLIPPED)
			continue;	// off the edge of the screen

		topnode = R_FindTopnode (minmaxs, minmaxs+3);
		if (!topnode)
			continue;	// no part in a visible leaf

		VectorCopy (currententity->origin, r_entorigin);
		VectorSubtract (r_origin, r_entorigin, modelorg);

		r_pcurrentvertbase = currentmodel->vertexes;

	// FIXME: stop transforming twice
		R_RotateBmodel ();

	// calculate dynamic lighting for bmodel
		R_PushDlights (currentmodel);

		if (topnode->contents == CONTENTS_NODE)
		{
		// not a leaf; has to be clipped to the world BSP
			r_clipflags = clipflags;
			R_DrawSolidClippedSubmodelPolygons (currentmodel, topnode);
		}
		else
		{
		// falls entirely in one leaf, so we just put all the
		// edges in the edge list and let 1/z sorting handle
		// drawing order
			R_DrawSubmodelPolygons (currentmodel, clipflags, topnode);
		}

	// put back world rotation and frustum clipping
	// FIXME: R_RotateBmodel should just work off base_vxx
		VectorCopy (base_vpn, vpn);
		VectorCopy (base_vup, vup);
		VectorCopy (base_vright, vright);
		VectorCopy (oldorigin, modelorg);
		R_TransformFrustum ();
	}

	insubmodel = false;
}


/*
================
R_EdgeDrawing
================
*/
static void R_EdgeDrawing (void)
{
	edge_t	ledges[NUMSTACKEDGES +
				((CACHE_SIZE - 1) / sizeof(edge_t)) + 1];
	surf_t	lsurfs[NUMSTACKSURFACES +
				((CACHE_SIZE - 1) / sizeof(surf_t)) + 1];

	if ( r_newrefdef.rdflags & RDF_NOWORLDMODEL )
		return;

	if (auxedges)
	{
		r_edges = auxedges;
	}
	else
	{
		r_edges =  (edge_t *)
				(((intptr_t)&ledges[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1));
	}

	if (r_surfsonstack)
	{
		surfaces =  (surf_t *)
				(((intptr_t)&lsurfs[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1));
		surf_max = &surfaces[r_cnumsurfs];
	// surface 0 doesn't really exist; it's just a dummy because index 0
	// is used to indicate no edge attached to surface
		surfaces--;
		R_SurfacePatch ();
	}

	R_BeginEdgeFrame ();

	if (r_dspeeds->value)
	{
		rw_time1 = Sys_Milliseconds ();
	}

	R_RenderWorld ();

	if (r_dspeeds->value)
	{
		rw_time2 = Sys_Milliseconds ();
		db_time1 = rw_time2;
	}

	R_DrawBEntitiesOnList ();

	if (r_dspeeds->value)
	{
		db_time2 = Sys_Milliseconds ();
		se_time1 = db_time2;
	}

	R_ScanEdges ();
}

//=======================================================================


/*
=============
R_CalcPalette

=============
*/
static void R_CalcPalette (void)
{
	static qboolean modified;
	byte	palette[256][4], *in, *out;
	int		i, j;
	float	alpha, one_minus_alpha;
	vec3_t	premult;
	int		v;

	alpha = r_newrefdef.blend[3];
	if (alpha <= 0)
	{
		if (modified)
		{	// set back to default
			modified = false;
			R_GammaCorrectAndSetPalette(d_8to24table );
			return;
		}
		return;
	}

	modified = true;
	if (alpha > 1)
		alpha = 1;

	premult[0] = r_newrefdef.blend[0]*alpha*255;
	premult[1] = r_newrefdef.blend[1]*alpha*255;
	premult[2] = r_newrefdef.blend[2]*alpha*255;

	one_minus_alpha = (1.0 - alpha);

	in = d_8to24table;
	out = palette[0];
	for (i=0 ; i<256 ; i++, in+=4, out+=4)
	{
		for (j=0 ; j<3 ; j++)
		{
			v = premult[j] + one_minus_alpha * in[j];
			if (v > 255)
				v = 255;
			out[j] = v;
		}
		out[3] = 255;
	}

	R_GammaCorrectAndSetPalette((const unsigned char *) palette[0]);
}

//=======================================================================

static void R_SetLightLevel (void)
{
	vec3_t		light;

	if ((r_newrefdef.rdflags & RDF_NOWORLDMODEL) || (!r_drawentities->value) || (!currententity))
	{
		r_lightlevel->value = 150.0;
		return;
	}

	// save off light value for server to look at (BIG HACK!)
	R_LightPoint (r_newrefdef.vieworg, light, true);
	r_lightlevel->value = 150.0 * light[0];
}


/*
@@@@@@@@@@@@@@@@
R_RenderFrame

@@@@@@@@@@@@@@@@
*/
void R_RenderFrame (refdef_t *fd)
{
	r_newrefdef = *fd;

	r_newrefdef.fovfactor = r_newrefdef.fov_x / 90.0f;

	if (!r_worldmodel && !( r_newrefdef.rdflags & RDF_NOWORLDMODEL ) )
		ri.Sys_Error (ERR_FATAL,"R_RenderView: NULL worldmodel");

	VectorCopy (fd->vieworg, r_refdef.vieworg);
	VectorCopy (fd->viewangles, r_refdef.viewangles);

	if (r_speeds->value || r_dspeeds->value)
		r_time1 = Sys_Milliseconds ();

	R_SetupFrame ();

	R_MarkLeaves ();	// done here so we know if we're in water

	R_PushDlights (r_worldmodel);

	R_EdgeDrawing ();

	if (r_dspeeds->value)
	{
		se_time2 = Sys_Milliseconds ();
		de_time1 = se_time2;
	}

	R_DrawEntitiesOnList ();

	if (r_dspeeds->value)
	{
		de_time2 = Sys_Milliseconds ();
		dp_time1 = Sys_Milliseconds ();
	}

	R_DrawParticles ();

	if (r_dspeeds->value)
		dp_time2 = Sys_Milliseconds ();

	R_DrawAlphaSurfaces();

	R_DrawTriangles();

	R_SetLightLevel ();

	if (r_dowarp)
		D_WarpScreen ();

	if (r_dspeeds->value)
		da_time1 = Sys_Milliseconds ();

	if (r_dspeeds->value)
		da_time2 = Sys_Milliseconds ();

	R_CalcPalette ();

	if (sw_aliasstats->value)
		R_PrintAliasStats ();

	if (r_speeds->value)
		R_PrintTimes ();

	if (r_dspeeds->value)
		R_PrintDSpeeds ();

	if (sw_reportsurfout->value && r_outofsurfaces)
		ri.Con_Printf (PRINT_ALL,"Short %d surfaces\n", r_outofsurfaces);

	if (sw_reportedgeout->value && r_outofedges)
		ri.Con_Printf (PRINT_ALL,"Short roughly %d edges\n", r_outofedges * 2 / 3);

	VectorCopy (fd->vieworg, consoleInfo.origin);
	consoleInfo.onscreen = true;
	consoleInfo.surface_width  = vid.width;
	consoleInfo.surface_height = vid.height;
	consoleInfo.u[0] = 1; consoleInfo.u[1] = 0; consoleInfo.u[2] = 0;
	consoleInfo.v[0] = 0; consoleInfo.v[1] = 0; consoleInfo.v[2] = 1;
	consoleInfo.n[0] = 0; consoleInfo.n[1] = 1; consoleInfo.n[2] = 0;
}

/*
** R_InitGraphics
*/
static void R_InitGraphics( int width, int height )
{
	vid.width  = width;
	vid.height = height;

	// free z buffer
	if ( d_pzbuffer )
	{
		free( d_pzbuffer );
		d_pzbuffer = NULL;
	}

	// free surface cache
	if ( sc_base )
	{
		D_FlushCaches ();
		free( sc_base );
		sc_base = NULL;
	}

	d_pzbuffer = (short *)malloc(vid.width*vid.height*2);

	R_InitCaches ();

	Draw_BuildGammaTable();

	R_GammaCorrectAndSetPalette(d_8to24table );
}

/*
** R_BeginFrame
*/
void R_BeginFrame( float camera_separation )
{
	/*
	** rebuild the gamma correction palette if necessary
	*/
	if ( vid_gamma->modified )
	{
		Draw_BuildGammaTable();
		R_GammaCorrectAndSetPalette(d_8to24table );

		vid_gamma->modified = false;
	}

	while ( sw_mode->modified || vid_fullscreen->modified )
	{
		rserr_t err;

		/*
		** if this returns rserr_invalid_fullscreen then it set the mode but not as a
		** fullscreen mode, e.g. 320x200 on a system that doesn't support that res
		*/
		if ((err = SWimp_SetMode(&vid.width, &vid.height, sw_mode->value, vid_fullscreen->value)) == rserr_ok)
		{
			R_InitGraphics( vid.width, vid.height );

			sw_state.prev_mode = sw_mode->value;
			vid_fullscreen->modified = false;
			sw_mode->modified = false;
		}
		else
		{
			if ( err == rserr_invalid_mode )
			{
				ri.Cvar_SetValue( "sw_mode", sw_state.prev_mode );
				ri.Con_Printf( PRINT_ALL, "ref_soft::R_BeginFrame() - could not set mode\n" );
			}
			else if ( err == rserr_invalid_fullscreen )
			{
				R_InitGraphics( vid.width, vid.height );

				ri.Cvar_SetValue( "vid_fullscreen", 0);
				ri.Con_Printf( PRINT_ALL, "ref_soft::R_BeginFrame() - fullscreen unavailable in this mode\n" );
				sw_state.prev_mode = sw_mode->value;
//				vid_fullscreen->modified = false;
//				sw_mode->modified = false;
			}
			else
			{
				ri.Sys_Error( ERR_FATAL, "ref_soft::R_BeginFrame() - catastrophic mode change failure\n" );
			}
		}

		Draw_InitLocal();
		Draw_BuildGammaTable();
		R_GammaCorrectAndSetPalette( d_8to24table );
	}

	consoleInfo.onscreen = true;
	consoleInfo.surface_width  = vid.width;
	consoleInfo.surface_height = vid.height;
	consoleInfo.u[0] = 1; consoleInfo.u[1] = 0; consoleInfo.u[2] = 0;
	consoleInfo.v[0] = 0; consoleInfo.v[1] = 0; consoleInfo.v[2] = 1;
	consoleInfo.n[0] = 0; consoleInfo.n[1] = 1; consoleInfo.n[2] = 0;
}

/*
** R_GammaCorrectAndSetPalette
*/
void R_GammaCorrectAndSetPalette( const unsigned char *palette )
{
	int i;

	for ( i = 0; i < 256; i++ )
	{
		sw_state.currentpalette[i*4+0] = sw_state.gammatable[palette[i*4+0]];
		sw_state.currentpalette[i*4+1] = sw_state.gammatable[palette[i*4+1]];
		sw_state.currentpalette[i*4+2] = sw_state.gammatable[palette[i*4+2]];
	}

	SWimp_SetPalette( sw_state.currentpalette );
}

/*
** R_CinematicSetPalette
*/
void R_CinematicSetPalette( const unsigned char *palette )
{
	byte palette32[1024];
	int		i, j, w;
	int		*d;

	// clear screen to black to avoid any palette flash
	w = abs(vid.rowbytes)>>2;	// stupid negative pitch win32 stuff...
	for (i=0 ; i<vid.height ; i++, d+=w)
	{
		d = (int *)(vid.buffer + i*vid.rowbytes);
		for (j=0 ; j<w ; j++)
			d[j] = 0;
	}
	// flush it to the screen
	SWimp_EndFrame ();

	if ( palette )
	{
		for ( i = 0; i < 256; i++ )
		{
			palette32[i*4+0] = palette[i*3+0];
			palette32[i*4+1] = palette[i*3+1];
			palette32[i*4+2] = palette[i*3+2];
			palette32[i*4+3] = 0xFF;
		}

		R_GammaCorrectAndSetPalette( palette32 );
	}
	else
	{
		R_GammaCorrectAndSetPalette(d_8to24table );
	}
}

/*
================
Draw_BuildGammaTable
================
*/
void Draw_BuildGammaTable (void)
{
	int		i, inf;
	float	g;

	g = vid_gamma->value;

	if (g == 1.0)
	{
		for (i=0; i<256; i++)
			sw_state.gammatable[i] = i;
	}
	else
	{
		for (i=0; i<256; i++)
		{
			inf = 255 * pow ( (i+0.5)/255.5, g ) + 0.5;
			if (inf < 0)
				inf = 0;
			if (inf > 255)
				inf = 255;
			sw_state.gammatable[i] = inf;
		}
	}
	//
	// build 16-bit tables
	//
	if (vid.bitdepth > 8 || vid.colorlighting)
	{
		float frac;
		float src,dest;
		int size;
		int l;
		byte * ptr;

		if (vid.bitdepth > 8)
		{
			size = NUMALPHALEVELS << (MAXCOLORBITS*2);

			free( vid.alphamap16 );
			vid.alphamap16 = (byte*)malloc( size );
			if ( !vid.alphamap16 )
				ri.Sys_Error( ERR_FATAL, "Draw_BuildGammaTable - cannont build alphamap16\n" );
			ptr = vid.alphamap16;
			for ( i = 0; i < NUMALPHALEVELS; i++ )
			{
				frac = ((float)i + 0.5f) / (float)NUMALPHALEVELS;
				for ( src = 0; src < (1<<MAXCOLORBITS); src++ )
				{
					for ( dest = 0; dest < (1<<MAXCOLORBITS); dest++ )
					{
						*ptr++ = (int)( src*frac + ( 1 - frac )*dest );
					}
				}
			}
		}

		size = NUMLIGHTLEVELS * 256 * sizeof( unsigned short );

		free( vid.redlightmap16 );
		vid.redlightmap16 = (unsigned short*)malloc( size );
		if ( !vid.redlightmap16 )
			ri.Sys_Error( ERR_FATAL, "Draw_BuildGammaTable - cannont build redlightmap16\n" );
		free( vid.greenlightmap16 );
		vid.greenlightmap16 = (unsigned short*)malloc( size );
		if ( !vid.greenlightmap16 )
			ri.Sys_Error( ERR_FATAL, "Draw_BuildGammaTable - cannont build greenlightmap16\n" );
		free( vid.bluelightmap16 );
		vid.bluelightmap16 = (unsigned short*)malloc( size );
		if ( !vid.bluelightmap16 )
			ri.Sys_Error( ERR_FATAL, "Draw_BuildGammaTable - cannont build bluelightmap16\n" );
		for ( i = 0; i < NUMLIGHTLEVELS; i++ )
		{
			frac = (float)i / (float)NUMLIGHTLEVELS;
			for ( src = 0; src < 256; src++ )
			{
				l = (int) ((1 - frac) * src);
				l = sw_state.gammatable[l];
				vid.redlightmap16[(i<<8)   + (int)src] = (l >> (8-REDCOLORBITS)) << REDCOLORSHIFT;
				vid.greenlightmap16[(i<<8) + (int)src] = (l >> (8-GREENCOLORBITS)) << GREENCOLORSHIFT;
				vid.bluelightmap16[(i<<8)  + (int)src] = (l >> (8-BLUECOLORBITS)) << BLUECOLORSHIFT;
			}
		}
		D_FlushCaches ();
	}
	if (vid.bitdepth == 8)
	{
		int r,g,b,col,size;
		size = ( 1 << 16 ) * sizeof( byte );
		free( vid.color_16to8table );
		vid.color_16to8table = (byte*)malloc( size );
		for ( i = 0; i < ( 1<< 16 ); i++ ) // x-00000-00000-00000 to x-11111-11111-11111
		{
			r = UNPACKRED16BIT( i );
			g = UNPACKGREEN16BIT( i );
			b = UNPACKBLUE16BIT( i );

			col = r | ( g << 5 ) | ( b << 11 );

			vid.color_16to8table[col] = d_16to8table[i];
		}
	}

	//
	// recalculate color tables for 16-bit
	//
	if ( vid.bitdepth > 8 )
		R_GammaChanged();
}

/*
** R_DrawBeam
*/
void R_DrawBeam( entity_t *e )
{
#define NUM_BEAM_SEGS 6

	int i;
	float r, g, b;
	float a;

	vec3_t perpvec;
	vec3_t direction, normalized_direction;
	vec3_t start_points[NUM_BEAM_SEGS], end_points[NUM_BEAM_SEGS];
	vec3_t oldorigin, origin;

	oldorigin[0] = e->oldorigin[0];
	oldorigin[1] = e->oldorigin[1];
	oldorigin[2] = e->oldorigin[2];

	origin[0] = e->origin[0];
	origin[1] = e->origin[1];
	origin[2] = e->origin[2];

	normalized_direction[0] = direction[0] = oldorigin[0] - origin[0];
	normalized_direction[1] = direction[1] = oldorigin[1] - origin[1];
	normalized_direction[2] = direction[2] = oldorigin[2] - origin[2];

	if ( VectorNormalize( normalized_direction ) == 0 )
		return;

	PerpendicularVector( perpvec, normalized_direction );
	VectorScale( perpvec, e->frame / 2, perpvec );

	for ( i = 0; i < NUM_BEAM_SEGS; i++ )
	{
		RotatePointAroundVector( start_points[i], normalized_direction, perpvec, (360.0/NUM_BEAM_SEGS)*i );
		VectorAdd( start_points[i], origin, start_points[i] );
		VectorAdd( start_points[i], direction, end_points[i] );
	}

	if ( e->skinnum == 255 )
	{
		r = e->color_r;
		g = e->color_g;
		b = e->color_b;
		a = e->alpha;
	}
	else
	{
		int c;

		c = e->skinnum;
		if (c < 0)
		{
			c = 0;
		}
		else if (c >= MAX_LIGHTSTYLES)
		{
			c = 0;
		}

		r = r_newrefdef.lightstyles[c].rgb[0] * 0.5;
		g = r_newrefdef.lightstyles[c].rgb[1] * 0.5;
		b = r_newrefdef.lightstyles[c].rgb[2] * 0.5;
		a = r_newrefdef.lightstyles[c].rgb[3];
		if (a < 0)
		{
			a = e->alpha;
		}
	}

	for ( i = 0; i < NUM_BEAM_SEGS; i++ )
	{
		R_IMFlatShadedQuad( start_points[i],
		                    end_points[i],
		                    end_points[(i+1)%NUM_BEAM_SEGS],
		                    start_points[(i+1)%NUM_BEAM_SEGS],
		                    r, g, b, a );
	}
}


//===================================================================

/*
============
R_SetSky
============
*/
// 3dstudio environment map names
const char *suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"};
int	r_skysideimage[6] = {5, 2, 4, 1, 0, 3};
extern	mtexinfo_t		r_skytexinfo[6];
csurface_t *sky_surface;

static void R_SetSky (const char *name, float rotate, vec3_t axis)
{
	int		i;
	char	pathname[MAX_QPATH];

	strncpy (skyname, name, sizeof(skyname)-1);
	skyrotate = rotate;
	VectorCopy (axis, skyaxis);

	sky_surface = (csurface_t *)calloc(1, sizeof(csurface_t));

	for (i=0 ; i<6 ; i++)
	{
		Com_sprintf (pathname, sizeof(pathname), "env/%s%s.tga", skyname, suf[r_skysideimage[i]]);
		r_skytexinfo[i].image = R_FindImage (pathname, it_sky, false);
		r_skytexinfo[i].surf = sky_surface;
	}
}

/*
@@@@@@@@@@@@@@@@@@@@@
GetRefAPI

@@@@@@@@@@@@@@@@@@@@@
*/
refexport_t GetRefAPI (refimport_t rimp)
{
	refexport_t	re;

	ri = rimp;

	re.api_version = API_VERSION;

	re.BeginRegistration = R_BeginRegistration;
    re.RegisterModel = R_RegisterModel;
    re.RegisterSkin = R_RegisterSkin;
	re.RegisterPic = Draw_FindPic;
	re.SetSky = R_SetSky;
	re.EndRegistration = R_EndRegistration;

	re.RenderFrame = R_RenderFrame;

	re.DrawGetPicSize = Draw_GetPicSize;
	re.DrawPic = Draw_Pic;
	re.DrawStretchPic = Draw_StretchPic;
	re.DrawChar = Draw_Char;
	re.DrawTileClear = Draw_TileClear;
	re.DrawFill = Draw_Fill;
	re.DrawFadeScreen= Draw_FadeScreen;

	re.Init = R_Init;
	re.Shutdown = R_Shutdown;

	re.CinematicSetPalette = R_CinematicSetPalette;
	re.BeginFrame = R_BeginFrame;
	re.EndFrame = SWimp_EndFrame;

	re.AppActivate = SWimp_AppActivate;

	re.SINMDL_GetDefHandle = SINMDL_GetDefHandle;
	re.DrawChar3D = Draw_Char3D;
	re.FlushChar3D = Flush_Char3D; /* FS: Dummy for GL vertex arrays */
	re.DrawPic3D = Draw_Pic3D;
	re.DrawFill3D = Draw_Fill3D;
	re.DrawAltFill3D = Draw_Fill3D;
	re.DrawStretchPic3D = Draw_StretchPic3D;
	re.DrawTilePic3D = Draw_TilePic3D;
	re.TesselateModel = TesselateModel;
	re.BurnLightmap = R_BurnPoint;
	re.GetNumFrames = R_GetNumFrames;
	re.BeginCinematic = Draw_BeginCinematic;
	re.EndCinematic = Draw_EndCinematic;
	re.DrawCinematicFrame = Draw_CinematicFrame;
#ifdef __A3D_GEOM
	re.A3D_RenderGeometry = A3D_RenderGeometry;
#endif

	Swap_Init ();

	return re;
}

#ifndef REF_HARD_LINKED
// this is only here so the functions in q_shared.c and q_shwin.c can link
void Sys_Error (const char *error, ...)
{
	va_list		argptr;
	char		text[4096]; /* FS: MAXPRINTMSG */

	va_start (argptr, error);
	Q_vsnprintf (text, sizeof(text), error, argptr);
	va_end (argptr);
	text[sizeof(text)-1] = 0;

	ri.Sys_Error (ERR_FATAL, "%s", text);
}

void Com_Printf (const char *fmt, ...)
{
	va_list		argptr;
	char		text[4096]; /* FS: MAXPRINTMSG */

	va_start (argptr, fmt);
	Q_vsnprintf (text, sizeof(text), fmt, argptr);
	va_end (argptr);
	text[sizeof(text)-1] = 0;

	ri.Con_Printf (PRINT_ALL, "%s", text);
}

#endif
