//-----------------------------------------------------------------------------
//
//  $Logfile:: /Source/gl_sinmdl.c                                             $
// $Revision:: 3                                                               $
//   $Author:: Burgermac                                                       $
//     $Date:: 8/8/01 4:46 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:
// Sin model loading and drawing
//

#include "gl_local.h"
#include "gl_sinmdl.h"

extern void SINMDL_DrawFrameLerp (dsinmdl_t *, float, float);
void SINMDL_RegisterModel (model_t *mod);
int SINMDL_GetNumFrames (int handle, int anim);
void SINMDL_FreeModel (model_t *mod);
int SINMDL_GetDefHandle (model_t * mdl);
void SINMDL_DrawModel (entity_t *e);

/*
==============================================================================

SIN MODELS

==============================================================================
*/

/*
=================
SINMDL_LoadSinModel
=================
*/
qboolean SINMDL_LoadSinModel (model_t *mod, void *buffer)
{
	int i;
	dsinmdl_t *psinmdl;
	dsinskin_t *pskins;

	mod->extradata = (void *)(intptr_t)ri.SINMDL_RegisterDef(mod->name);
	if ((intptr_t)mod->extradata < 0)
		return false;

	mod->type = mod_sinmdl;
	psinmdl = ri.SINMDL_GetDef((int)(intptr_t)mod->extradata);
	if (!psinmdl)
		return false;

	VectorCopy(psinmdl->mins, mod->mins);
	VectorCopy(psinmdl->maxs, mod->maxs);

	pskins = (dsinskin_t *) ((byte *)psinmdl + psinmdl->ofs_skins);

	// register all skins
	for (i = 0; i < psinmdl->num_skins; i++)
	{
		mod->skins[i] = GL_FindImage (pskins[i].path, it_skin);
	}

	mod->extradatasize = 0;

	return true;
}


/*
=================
SINMDL_RegisterModel
=================
*/
void SINMDL_RegisterModel (model_t *mod)
{
	int i;
	dsinmdl_t *psinmdl;
	dsinanim_t *panim;
	dsinanimdef_t *panimdef;
	dsinbasemdl_t *pbase;
	dsinskin_t *pskins;

	if ((intptr_t)mod->extradata < 0)
		return;

	psinmdl = ri.SINMDL_GetDef((int)(intptr_t)mod->extradata);
	if (!psinmdl)
		return;

	pskins = (dsinskin_t *) ((byte *)psinmdl + psinmdl->ofs_skins);

	// register all skins
	for (i = 0; i < psinmdl->num_skins; i++)
	{
		mod->skins[i] = GL_FindImage (pskins[i].path, it_skin);
	}

	if (psinmdl->base_handle >= 0)
	{
		pbase = ri.SINMDL_GetBaseModel(psinmdl->base_handle);
		// touch base model
		Com_PageInMemory ((byte *)pbase, pbase->ofs_end);
	}

	// go through all the animations
	for (i = 0; i < psinmdl->num_anims; i++)
	{
		panimdef = (dsinanimdef_t *) ((byte *)psinmdl + psinmdl->ofs_animdefs[i]);
		if (panimdef->handle >= 0)
		{
			panim = ri.SINMDL_GetAnim(panimdef->handle);
			// touch each anim
			Com_PageInMemory ((byte *)panim, panim->ofs_end);
		}
	}
}

/*
=================
SINMDL_GetNumFrames
=================
*/
int SINMDL_GetNumFrames (int handle, int anim)
{
	dsinmdl_t *psinmdl;
	dsinanim_t *panim;
	dsinanimdef_t *panimdef;

	if (handle < 0)
		return 0;

	psinmdl = ri.SINMDL_GetDef(handle);
	if (!psinmdl)
		return 0;

	if (anim < 0 || anim >= psinmdl->num_anims)
		return 0;

	panimdef = (dsinanimdef_t *) ((byte *)psinmdl + psinmdl->ofs_animdefs[anim]);
	if (panimdef->handle >= 0)
	{
		panim = ri.SINMDL_GetAnim(panimdef->handle);
		return panim->num_frames;
	}
	return 0;
}

/*
=================
SINMDL_FreeModel
=================
*/
void SINMDL_FreeModel (model_t *mod)
{
	ri.SINMDL_FreeDef((int)(intptr_t)mod->extradata);
}

/*
=================
SINMDL_GetDefHandle
=================
*/
int SINMDL_GetDefHandle (model_t *mdl)
{
	if (mdl && mdl->type == mod_sinmdl)
		return (int)(intptr_t)mdl->extradata;
	return -1;
}


/*
=============================================================

  SINMDL DRAWING ROUTINES

=============================================================
*/

#define NUMVERTEXNORMALS	162

extern float	r_avertexnormals[NUMVERTEXNORMALS][3];
extern vec3_t	shadevector;
extern float	shadelight[3];
vec3_t		fullbright={1,1,1};

// precalculated dot products for quantized angles
#define SHADEDOT_QUANT 16
extern float	r_avertexnormal_dots[SHADEDOT_QUANT][256];
extern float	*shadedots;

dsinbasemdl_t	*currentbase;
dtrivertx_t	*currentverts;
dtrivertx_t	*oldverts;
dsinanim_t	*currentanim;
dsinanim_t	*oldanim;
dsintriframe_t	*currentframe;
dsintriframe_t	*oldframe;
float		currentscale;
qboolean	weapon_lefthanded;
float	sinmdl_transform[3][4];

#define MAX_RENDER_GROUPS 32
int rendergroups[MAX_RENDER_GROUPS];

#ifndef AMD3D
static void SINMDL_LerpVerts (int nverts, dtrivertx_t *v, dtrivertx_t *ov, dtrivertx_t *verts, float *lerp, float move[3], float frontv[3], float backv[3])
{
	int i;

	for (i = 0; i < nverts; i++, v++, ov++, lerp += 4)
	{
		lerp[0] = move[0] + ov->v[0]*backv[0] + v->v[0]*frontv[0];
		lerp[1] = move[1] + ov->v[1]*backv[1] + v->v[1]*frontv[1];
		lerp[2] = move[2] + ov->v[2]*backv[2] + v->v[2]*frontv[2];
	}
}
#endif

/*
=============
SINMDL_DrawFrameLerp

interpolates between two frames and origins
FIXME: batch lerp all vertexes
=============
*/
#ifndef AMD3D
void SINMDL_DrawFrameLerp (dsinmdl_t *pmdl, float backlerp, float framebacklerp)
{
	float 	l;
	int		*order;
	int		count;
	float	   frontlerp;
	float	   framefrontlerp;
	float	   alpha;
	vec3_t	move, delta;
	vec3_t	frontv, backv;
	int		i,j;
	int		index_xyz;
	float	   *lerp;
	image_t	**skins;
	image_t	*skin;
	float	*plight;
//	qboolean locked = false;


	frontlerp = 1.0 - backlerp;
	framefrontlerp = 1.0 - framebacklerp;

	// move should be the delta back to the previous frame * backlerp
	VectorSubtract (currententity->oldorigin, currententity->origin, delta);

	move[0] =  DotProduct(delta, currententity->mat[0]);	// forward
	move[1] =  DotProduct(delta, currententity->mat[1]);	// left
	move[2] =  DotProduct(delta, currententity->mat[2]);	// up

	VectorScale(oldframe->translate, currentscale*pmdl->load_scale, delta);

	for (i = 0; i < 3; i++)
	{
		move[i] = framebacklerp*delta[i] +
			  backlerp*move[i] +
			  ((framefrontlerp*currentframe->translate[i])+pmdl->load_origin[i])*currentscale*pmdl->load_scale;
		frontv[i] = framefrontlerp*currentframe->scale[i]*currentscale*pmdl->load_scale;
		backv[i] = framebacklerp*oldframe->scale[i]*currentscale*pmdl->load_scale;
	}

	lerp = s_lerped[0];

	SINMDL_LerpVerts(currentbase->num_xyz, currentverts, oldverts, currentverts, lerp, move, frontv, backv);

	// bind the right skin for this group
	// select skin
	if (currententity->skin)
	{
		static image_t *playerskins[2];

		skins = playerskins;
		playerskins[0] = currententity->skin;
		playerskins[1] = currententity->damageskin;
	}
	else
	{
		if (currententity->skinnum < MAX_MD2SKINS)
			skins = &currentmodel->skins[currententity->skinnum];
		else
			skins = &currentmodel->skins[0];
	}

	for (i = 0; i < currentbase->num_groups; i++)
	{
		dsintrigroup_t *pgroup;

		if (rendergroups[i] & MDL_GROUP_NODRAW)
			continue;

		pgroup = &currentbase->groups[i];

		c_alias_polys += pgroup->num_tris;

		order = (int *)((byte *)pgroup + pgroup->ofs_glcmds);

		if (gl_drawflat->value)
		{
			qglDisable (GL_TEXTURE_2D);
			// check for twosidedness
			if (rendergroups[i] & MDL_GROUP_TWOSIDED)
				qglDisable(GL_CULL_FACE);
			while (1)
			{
				// get the vertex count and primitive type
				count = *order++;
				if (!count)
					break;		// done
				if (count < 0)
				{
					count = -count;
					qglBegin (GL_TRIANGLE_FAN);
				}
				else
				{
					qglBegin (GL_TRIANGLE_STRIP);
				}

				do
				{
					int index;
					float *c;

					index_xyz = order[2];

					if (gl_drawflat->value == 1.0f)
					{
						index = (((intptr_t)s_lerped[index_xyz])>>3)&0xff;
					}
					else
					{
						index = i & 0xff;
					}
					c = &r_solidcolors[index*3];

					qglColor3f (c[0], c[1], c[2]);
					qglVertex3fv (s_lerped[index_xyz]);
					order += 3;
				} while (--count);
				qglEnd ();
			}
			qglEnable (GL_TEXTURE_2D);
		}
		else if (rendergroups[i] & (MDL_GROUP_ENVMAPPED_SILVER|MDL_GROUP_ENVMAPPED_GOLD))
		{
			if (rendergroups[i] & MDL_GROUP_ENVMAPPED_SILVER)
			{
				// 2015 code
				if (game2015->value)
				{
					// use the therm-optic goggles env map
					if (currententity->lightofs && !(currententity->flags & RF_LIGHTOFFSET))
						GL_BindImage(r_gogglesmap);
					else // use regular crome map
						GL_BindImage(r_envmap);
				}
				else
				{
					GL_BindImage(r_envmap);
				}
			}
			else
			{
				GL_BindImage(r_goldenvmap);
			}
			// check for twosidedness
			if (rendergroups[i] & MDL_GROUP_TWOSIDED)
				qglDisable(GL_CULL_FACE);
			if (currententity->flags & RF_TRANSLUCENT)
			{
				alpha = currententity->alpha;
			}
			else
			{
				if (rendergroups[i] & MDL_GROUP_TRANSLUCENT_33)
				{
					// 2015 code
					if (rendergroups[i] & MDL_GROUP_TRANSLUCENT_66)
					{
						qglDepthMask (0);
						qglEnable (GL_BLEND);
						alpha = currententity->alpha;
					}
					else
					{
						qglDepthMask (0);
						qglEnable (GL_BLEND);
						alpha = 0.66f;
					}
				}
				else if (rendergroups[i] & MDL_GROUP_TRANSLUCENT_66)
				{
					qglDepthMask (0);
					qglEnable (GL_BLEND);
					alpha = 0.33f;
				}
				else
					alpha = 1.0;
			}
			if (rendergroups[i] & MDL_GROUP_FULLBRIGHT || r_fullbright->value)
				plight = fullbright;
			else
				plight = shadelight;

			while (1)
			{
				// get the vertex count and primitive type
				count = *order++;
				if (!count)
					break;		// done
				if (count < 0)
				{
					count = -count;
					qglBegin (GL_TRIANGLE_FAN);
				}
				else
				{
					qglBegin (GL_TRIANGLE_STRIP);
				}

				do
				{
					int lightindex;
					int oldlightindex;
					int k;
					vec3_t eye_normal;
					vec3_t eye_vert;
					vec3_t normal;
					float two_nu, m, fx, fy, fz;

					index_xyz = order[2];
					lightindex = currentverts[index_xyz].lightnormalindex;
					oldlightindex = oldverts[index_xyz].lightnormalindex;

					for (k = 0; k < 3; k++)
						normal[k] = (r_avertexnormals[lightindex][k]*frontlerp) +
							    (r_avertexnormals[oldlightindex][k]*backlerp);

					eye_normal[0] = DotProduct(normal, sinmdl_transform[0]);
					eye_normal[1] = DotProduct(normal, sinmdl_transform[1]);
					eye_normal[2] = DotProduct(normal, sinmdl_transform[2]);

					eye_vert[0] = DotProduct(s_lerped[index_xyz], sinmdl_transform[0]) + sinmdl_transform[0][3];
					eye_vert[1] = DotProduct(s_lerped[index_xyz], sinmdl_transform[1]) + sinmdl_transform[1][3];
					eye_vert[2] = DotProduct(s_lerped[index_xyz], sinmdl_transform[2]) + sinmdl_transform[2][3];

					VectorNormalize(eye_vert);
					two_nu = 2.0f * DotProduct(eye_normal, eye_vert);
					fx = eye_vert[0] - eye_normal[0] * two_nu;
					fy = eye_vert[1] - eye_normal[1] * two_nu;
					fz = eye_vert[2] - eye_normal[2] * two_nu;
					m = 2.0F * sqrt(fx*fx + fy*fy + (fz+1.0F)*(fz+1.0F));
					if (m == 0.0F)
					{
						qglTexCoord2f (0.5f, 0.5f);
					}
					else
					{
						float mInv = 1.0F / m;
						qglTexCoord2f (fx * mInv + 0.5f, fy * mInv + 0.5F);
					}
					// normals and vertexes come from the frame list
					l = shadedots[lightindex];
					qglColor4f (l* plight[0], l*plight[1], l*plight[2], alpha);

					qglVertex3fv (s_lerped[index_xyz]);
					order += 3;
				} while (--count);

				qglEnd ();
			}
		}
		else
		{
			//
			// get the skin
			//
			skin = skins[rendergroups[i] & 0x3];
			if (!skin)
				skin = r_notexture;	// fallback...

			GL_BindImage(skin);
			// check for twosidedness
			if (rendergroups[i] & MDL_GROUP_TWOSIDED)
				qglDisable(GL_CULL_FACE);
			// check for masked
			if (rendergroups[i] & MDL_GROUP_MASKED)
			{
				qglDepthMask (0);
				qglEnable (GL_BLEND);
				GL_TexEnv (GL_MODULATE);
				qglBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
			}
			if (currententity->flags & RF_TRANSLUCENT)
			{
				alpha = currententity->alpha;
			}
			else
			{
				if (rendergroups[i] & MDL_GROUP_TRANSLUCENT_33)
				{
					// 2015 code
					if (rendergroups[i] & MDL_GROUP_TRANSLUCENT_66)
					{
						qglDepthMask (0);
						qglEnable (GL_BLEND);
						alpha = currententity->alpha;
					}
					else
					{
						qglDepthMask (0);
						qglEnable (GL_BLEND);
						alpha = 0.66f;
					}
				}
				else if (rendergroups[i] & MDL_GROUP_TRANSLUCENT_66)
				{
					qglDepthMask (0);
					qglEnable (GL_BLEND);
					alpha = 0.33f;
				}
				else
					alpha = 1.0;
			}
			if (rendergroups[i] & MDL_GROUP_FULLBRIGHT || r_fullbright->value)
				plight = fullbright;
			else
				plight = shadelight;

			while (1)
			{
				// get the vertex count and primitive type
				count = *order++;
				if (!count)
					break;		// done
				if (count < 0)
				{
					count = -count;
					qglBegin (GL_TRIANGLE_FAN);
				}
				else
				{
					qglBegin (GL_TRIANGLE_STRIP);
				}

				do
				{
					index_xyz = order[2];

					// texture coordinates come from the draw list
					qglTexCoord2f (((float *)order)[0], ((float *)order)[1]);

					// normals and vertexes come from the frame list
					l = shadedots[currentverts[index_xyz].lightnormalindex];
					qglColor4f (l* plight[0], l*plight[1], l*plight[2], alpha);

					qglVertex3fv (s_lerped[index_xyz]);
					order += 3;
				} while (--count);

				qglEnd ();
			}
		}

		if (gl_showtris->value)
		{
			dsintriangle_t *ptri;

			ptri = (dsintriangle_t *)((byte *)pgroup + pgroup->ofs_tris);

			for (j=0 ; j<pgroup->num_tris ; j++, ptri++)
			{
				qglBegin (GL_LINE_STRIP);
				qglColor4f (1,1,1,1);
				qglVertex3fv (s_lerped[ptri->index_xyz[0]]);
				qglVertex3fv (s_lerped[ptri->index_xyz[1]]);
				qglVertex3fv (s_lerped[ptri->index_xyz[2]]);
				qglVertex3fv (s_lerped[ptri->index_xyz[0]]);
				qglEnd ();
			}
		}
		if (rendergroups[i] & MDL_GROUP_TWOSIDED)
			qglEnable(GL_CULL_FACE);
		if (rendergroups[i] & MDL_GROUP_MASKED)
		{
			qglDisable (GL_ALPHA_TEST);
			qglDepthMask (1);
			qglDisable (GL_BLEND);
			GL_TexEnv (GL_REPLACE);
		}
		if (!(currententity->flags & RF_TRANSLUCENT) &&
			((rendergroups[i] & MDL_GROUP_TRANSLUCENT_33) ||
			 (rendergroups[i] & MDL_GROUP_TRANSLUCENT_66)) )
		{
			qglDepthMask (1);
			qglDisable (GL_BLEND);
		}
	}
//	if (locked && qglUnlockArraysEXT != 0)
//		qglUnlockArraysEXT();
}
#endif

/*
=============
SINMDL_DrawShadow2
=============
*/
extern	vec3_t			lightspot;

static void SINMDL_DrawShadow2 (dsinmdl_t *pmdl)
{
	int		i;
	int		*order;
	vec3_t	point;
	float	height, lheight;
	int		count;
	float	shade;
	extern vec3_t lightspot;

	lheight = currententity->origin[2] - lightspot[2];

	if (lheight < 0 || lheight > 64)
		return;

	height = -lheight + 1.0;

	shade = lheight / 128;
	if (shade < 0.0f)
		shade = 0.0f;
	if (shade > 0.5f)
		shade = 0.5f;

	qglColor4f(0, 0, 0, 0.5f - shade);

	/* Knightmare */
	if (gl_config.have_stencil && gl_shadows_stencil->intValue)
	{
		qglEnable(GL_STENCIL_TEST);
		qglStencilFunc(GL_EQUAL, 1, 2);
		qglStencilOp(GL_KEEP,GL_KEEP,GL_INCR);
	}

	for (i = 0; i < currentbase->num_groups; i++)
	{
		dsintrigroup_t *pgroup;

		pgroup = &currentbase->groups[i];

		c_alias_polys += pgroup->num_tris;

		order = (int *)((byte *)pgroup + pgroup->ofs_glcmds);

		if (rendergroups[i] & MDL_GROUP_MASKED)
			continue;
		if (rendergroups[i] & MDL_GROUP_NODRAW)
			continue;
		if (currententity->flags & RF_TRANSLUCENT)
			continue;
		while (1)
		{
			// get the vertex count and primitive type
			count = *order++;
			if (!count)
				break;		// done
			if (count < 0)
			{
				count = -count;
				qglBegin (GL_TRIANGLE_FAN);
			}
			else
				qglBegin (GL_TRIANGLE_STRIP);

			do
			{
				memcpy(point, s_lerped[order[2]], sizeof(point));

				point[0] -= shadevector[0]*(point[2]+lheight);
				point[1] -= shadevector[1]*(point[2]+lheight);
				point[2] = height;
				qglVertex3fv (point);

				order += 3;
			} while (--count);

			qglEnd ();
		}
	}

	/* Knightmare */
	if (gl_config.have_stencil && gl_shadows_stencil->intValue)
		qglDisable(GL_STENCIL_TEST);
}

#define NUM_SEGMENTS 36
#define SHADOW_RADIUS 30
static void SINMDL_DrawShadow (dsinmdl_t *pmdl)
{
	int i;
	float angle;
	float cosang;
	float sinang;
	float scale;
	float shade;
	vec3_t	center, point;
	float height, lheight;
	extern vec3_t lightspot;

	lheight = currententity->origin[2] - lightspot[2];

	if (lheight < 0 || lheight > 64)
		return;

	shade = lheight / 128;
	if (shade < 0.0f)
		shade = 0.0f;
	if (shade > 0.5f)
		shade = 0.5f;

	qglColor4f(0, 0, 0, 0.5f - shade);

	scale = lheight / 32;
	if (scale < 0.75) scale = 0.75;
	if (scale > 2) scale = 2;

	height = -lheight + 2.0;

	qglDisable(GL_CULL_FACE);
	qglBegin (GL_TRIANGLE_FAN);
	VectorCopy(vec3_origin, center);
	center[0] -= shadevector[0]*lheight;
	center[1] -= shadevector[1]*lheight;
	center[2] = height;
	qglVertex3fv (center);
	for (i = 0; i <= NUM_SEGMENTS; i++)
	{
		angle = (i * 2 * M_PI) / (float)NUM_SEGMENTS;
		sinang = scale * sin(angle);
		cosang = scale * cos(angle);
		VectorCopy(center, point);
		point[0] += (SHADOW_RADIUS / 2) * cosang;
		point[1] += (SHADOW_RADIUS) * sinang;
		qglVertex3fv (point);
	}
	qglEnd ();
	qglEnable(GL_CULL_FACE);
}

/*
** SINMDL_CullModel
*/
static qboolean SINMDL_CullModel (vec3_t bbox[8], dsinmdl_t *pmdl, entity_t *e)
{
	vec3_t mins, maxs;
	int i;

	for (i = 0; i < 3; i++)
	{
		mins[i] = pmdl->mins[i]*currentscale*pmdl->load_scale;
		maxs[i] = pmdl->maxs[i]*currentscale*pmdl->load_scale;
	}
	CalculateRotatedBounds2(currententity->mat, mins, maxs);

	/*
	** compute a full bounding box
	** subsequently rotate it
	*/
	for (i = 0; i < 8; i++)
	{
		if (i & 1)
			bbox[i][0] = mins[0];
		else
			bbox[i][0] = maxs[0];

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

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

		VectorAdd(e->origin, bbox[i], bbox[i]);
	}

	{
		int p, f, aggregatemask = ~0;

		for (p = 0; p < 8; p++)
		{
			int mask = 0;

			for (f = 0; f < 4; f++)
			{
				float dp = DotProduct(frustum[f].normal, bbox[p]);

				if ((dp - frustum[f].dist) < 0)
				{
					mask |= (1 << f);
				}
			}
			aggregatemask &= mask;
		}

		if (aggregatemask)
			return true;

		return false;
	}
}

static void SINMDL_DrawBbox (vec3_t bbox[8])
{
	int		i;

	qglColor4f (1.0f, 1.0f, 1.0f, 0.3f);

	qglDisable(GL_DEPTH_TEST);
	qglDisable(GL_CULL_FACE);
	qglDisable(GL_TEXTURE_2D);
	qglEnable(GL_BLEND);

#if 0
	qglBegin(GL_QUADS);

	qglVertex3fv(bbox[0]);
	qglVertex3fv(bbox[1]);
	qglVertex3fv(bbox[3]);
	qglVertex3fv(bbox[2]);

	qglVertex3fv(bbox[0]);
	qglVertex3fv(bbox[1]);
	qglVertex3fv(bbox[5]);
	qglVertex3fv(bbox[4]);

	qglVertex3fv(bbox[1]);
	qglVertex3fv(bbox[3]);
	qglVertex3fv(bbox[7]);
	qglVertex3fv(bbox[5]);

	qglVertex3fv(bbox[2]);
	qglVertex3fv(bbox[3]);
	qglVertex3fv(bbox[7]);
	qglVertex3fv(bbox[6]);

	qglVertex3fv(bbox[0]);
	qglVertex3fv(bbox[2]);
	qglVertex3fv(bbox[6]);
	qglVertex3fv(bbox[4]);

	qglVertex3fv(bbox[4]);
	qglVertex3fv(bbox[5]);
	qglVertex3fv(bbox[7]);
	qglVertex3fv(bbox[6]);

	qglEnd();
#else /* FS: Use vertex arrays */
	rb_vertex = rb_index = 0;

	indexArray[rb_index++] = rb_vertex+0;
	indexArray[rb_index++] = rb_vertex+1;
	indexArray[rb_index++] = rb_vertex+3;
	indexArray[rb_index++] = rb_vertex+0;
	indexArray[rb_index++] = rb_vertex+3;
	indexArray[rb_index++] = rb_vertex+2;

	indexArray[rb_index++] = rb_vertex+0;
	indexArray[rb_index++] = rb_vertex+1;
	indexArray[rb_index++] = rb_vertex+5;
	indexArray[rb_index++] = rb_vertex+0;
	indexArray[rb_index++] = rb_vertex+5;
	indexArray[rb_index++] = rb_vertex+4;

	indexArray[rb_index++] = rb_vertex+1;
	indexArray[rb_index++] = rb_vertex+3;
	indexArray[rb_index++] = rb_vertex+7;
	indexArray[rb_index++] = rb_vertex+1;
	indexArray[rb_index++] = rb_vertex+7;
	indexArray[rb_index++] = rb_vertex+5;

	indexArray[rb_index++] = rb_vertex+2;
	indexArray[rb_index++] = rb_vertex+3;
	indexArray[rb_index++] = rb_vertex+7;
	indexArray[rb_index++] = rb_vertex+2;
	indexArray[rb_index++] = rb_vertex+7;
	indexArray[rb_index++] = rb_vertex+6;

	indexArray[rb_index++] = rb_vertex+0;
	indexArray[rb_index++] = rb_vertex+2;
	indexArray[rb_index++] = rb_vertex+6;
	indexArray[rb_index++] = rb_vertex+0;
	indexArray[rb_index++] = rb_vertex+6;
	indexArray[rb_index++] = rb_vertex+4;

	indexArray[rb_index++] = rb_vertex+4;
	indexArray[rb_index++] = rb_vertex+5;
	indexArray[rb_index++] = rb_vertex+7;
	indexArray[rb_index++] = rb_vertex+4;
	indexArray[rb_index++] = rb_vertex+7;
	indexArray[rb_index++] = rb_vertex+6;

	for (i=0; i<8; i++) {
		VA_SetElem3v(vertexArray[rb_vertex], bbox[i]);
		VA_SetElem4(colorArray[rb_vertex], 1.0f, 1.0f, 1.0f, 0.3f);
		rb_vertex++;
	}
	R_RenderMeshGeneric (false);
#endif

	qglDisable(GL_BLEND);
	qglEnable(GL_TEXTURE_2D);
	qglEnable(GL_CULL_FACE);
	qglEnable(GL_DEPTH_TEST);
}
/*
=================
SINMDL_DrawModel

=================
*/
extern void MYgluPerspective (GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar);

void SINMDL_DrawModel (entity_t *e)
{
	int			i, j;
	dsinmdl_t	*pmdl;
	dsinanimdef_t	*panimdef;
	dsintriframe_t	*pframe;
	dsingroup_t	*pgroup;
	float		an;
	vec3_t			bbox[8];
	qboolean		envmapped;
	float		framelerp;

	if ((intptr_t)currentmodel->extradata < 0)
		return;
	pmdl = ri.SINMDL_GetDef((int)(intptr_t)currentmodel->extradata);

	// get current scale
	currentscale = e->scale;

	if (!(e->flags & RF_WEAPONMODEL))
	{
		if (SINMDL_CullModel(bbox, pmdl, e))
			return;
	}

	if (gl_showents->value)
	{
		ri.Con_Printf (PRINT_ALL, "%8p: %32s x%8.2f y%8.2f z%8.2f\n",
				e, pmdl->name, e->origin[0], e->origin[1], e->origin[2]);
	}

	//
	// determine whether the weapon is right handed or left handed
	//
	weapon_lefthanded = false;
	if (e->flags & RF_WEAPONMODEL)
	{
		if (r_lefthand->value == 2)
			return;
		if (r_lefthand->value == 1.0F)
			weapon_lefthanded = true;
		if (e->flags & RF_XFLIP)
		weapon_lefthanded ^= true;
	}

	//
	// get lighting information
	//
	if (currententity->flags & RF_FULLBRIGHT)
	{
		for (i = 0; i < 3; i++)
			shadelight[i] = 1.0;
	}
	else
	{
		vec3_t origin;
		//
		// we offset this a bit to account for slopes
		//
		VectorCopy(currententity->origin, origin);
		origin[2] += 24;
		R_LightPoint (origin, shadelight, true);

		// player lighting hack for communication back to server
		// big hack!
		if (currententity->flags & RF_WEAPONMODEL)
		{
			// pick the greatest component, which should be the same
			// as the mono value returned by software
			if (shadelight[0] > shadelight[1])
			{
				if (shadelight[0] > shadelight[2])
					r_lightlevel->value = 150*shadelight[0];
				else
					r_lightlevel->value = 150*shadelight[2];
			}
			else
			{
				if (shadelight[1] > shadelight[2])
					r_lightlevel->value = 150*shadelight[1];
				else
					r_lightlevel->value = 150*shadelight[2];
			}
		}

		if (gl_monolightmap->string[0] != '0')
		{
			float s = shadelight[0];

			if (s < shadelight[1])
				s = shadelight[1];
			if (s < shadelight[2])
				s = shadelight[2];

			shadelight[0] = s;
			shadelight[1] = s;
			shadelight[2] = s;
		}
	}

	if (currententity->flags & RF_MINLIGHT)
	{
		for (i = 0; i < 3; i++)
			if (shadelight[i] > 0.1)
				break;
		if (i == 3)
		{
			shadelight[0] = 0.1;
			shadelight[1] = 0.1;
			shadelight[2] = 0.1;
		}
	}

	if (currententity->flags & RF_GLOW)
	{	// bonus items will pulse with time
		float	scale;
		float	min;

		scale = 0.1 * sin(r_newrefdef.time*7);
		for (i = 0; i < 3; i++)
		{
			min = shadelight[i] * 0.8;
			shadelight[i] += scale;
			if (shadelight[i] < min)
				shadelight[i] = min;
		}
	}

	if (currententity->flags & RF_LIGHTOFFSET)
	{ // minimum light adjustment
		for (i = 0; i < 3; i++)
		{
			float min;

			min = shadelight[i]/3.0f;
			shadelight[i] += ((float)currententity->lightofs/127.0f);
			if (shadelight[i] < min)
				shadelight[i] = min;
			if (shadelight[i] > 1)
				shadelight[i] = 1;
		}
	}

	shadedots = r_avertexnormal_dots[((int)(currententity->angles[1] * (SHADEDOT_QUANT / 360.0))) & (SHADEDOT_QUANT - 1)];

	an = currententity->angles[1]/180*M_PI;
	shadevector[0] = cos(-an);
	shadevector[1] = sin(-an);
	shadevector[2] = 1;
	VectorNormalize (shadevector);

	//
	// draw all the triangles
	//
	if (currententity->flags & RF_WEAPONMODEL) // hack the depth range to prevent view model from poking into walls
		qglDepthRange (gldepthmin, gldepthmin + 0.3*(gldepthmax-gldepthmin));

	if (weapon_lefthanded)
	{
		qglMatrixMode(GL_PROJECTION);
		qglPushMatrix();
		qglLoadIdentity();
		qglScalef(-1, 1, 1);

	// 2015 code
	// draw view models in a fixed fov
	// fov_y of 75 gives an fov_x of about 90
	//	MYgluPerspective(r_newrefdef.fov_y, (float) r_newrefdef.width / r_newrefdef.height, 4, 4096);
		MYgluPerspective(75.0, (float) r_newrefdef.width / r_newrefdef.height, 4, 4096);
		qglMatrixMode(GL_MODELVIEW);

		qglCullFace(GL_BACK);
	}
	// 2015 code
	// draw view models in fov 90 or less
	else if ((currententity->flags & RF_WEAPONMODEL) && (r_newrefdef.fov_x > 90))
	{
		qglMatrixMode(GL_PROJECTION);
		qglPushMatrix();
		qglLoadIdentity();
		qglScalef(1, 1, 1);
		// fov_y of 75 gives an fov_x of about 90
		MYgluPerspective(75.0, (float) r_newrefdef.width / r_newrefdef.height, 4, 4096);
		qglMatrixMode(GL_MODELVIEW);

		qglCullFace(GL_FRONT);

		// will make the perspective be set back to normal later
		weapon_lefthanded = true;
	}

	qglPushMatrix ();
	e->angles[PITCH] = -e->angles[PITCH];	// sigh.
	e->angles[ROLL] = -e->angles[ROLL];	// stupid quake bug
	R_RotateForEntity (e);
	e->angles[PITCH] = -e->angles[PITCH];	// sigh.
	e->angles[ROLL] = -e->angles[ROLL];	// stupid quake bug

	// draw it

	qglShadeModel (GL_SMOOTH);

	GL_TexEnv(GL_MODULATE);
	if (currententity->flags & RF_TRANSLUCENT)
	{
		qglDepthMask (0);
		qglEnable (GL_BLEND);
	}

	// get current base
	if (pmdl->base_handle >= 0)
		currentbase = ri.SINMDL_GetBaseModel(pmdl->base_handle);
	else
	{
		ri.Con_Printf (PRINT_DEVELOPER, "SINMDL_DrawModel: no such base model %d in %s\n",
				pmdl->base_handle, currentmodel->name);
		return;
	}

	// get current anim
	if (currententity->anim >= pmdl->num_anims || currententity->anim < 0)
	{
		ri.Con_Printf (PRINT_DEVELOPER, "SINMDL_DrawModel: no such anim %d in %s\n",
				currententity->anim, currentmodel->name);
		currententity->anim %= pmdl->num_anims;
//		currententity->anim = 0;
	}

	panimdef = (dsinanimdef_t *) ((byte *)pmdl + pmdl->ofs_animdefs[currententity->anim]);
	if (panimdef->handle >= 0)
		currentanim = ri.SINMDL_GetAnim(panimdef->handle);
	else
	{
		ri.Con_Printf (PRINT_DEVELOPER, "SINMDL_DrawModel: NULL anim handle in %s\n",
				currentmodel->name);
		currentanim = NULL;
	}

	// get old anim
	if (currententity->oldanim >= pmdl->num_anims || currententity->oldanim < 0)
	{
		ri.Con_Printf (PRINT_DEVELOPER, "SINMDL_DrawModel: no such last anim %d in %s\n",
				currententity->oldanim, currentmodel->name);
		currententity->oldanim %= pmdl->num_anims;
//		currententity->oldanim = 0;
	}

	panimdef = (dsinanimdef_t *) ((byte *)pmdl + pmdl->ofs_animdefs[currententity->oldanim]);
	if (panimdef->handle >= 0)
		oldanim = ri.SINMDL_GetAnim(panimdef->handle);
	else
	{
		ri.Con_Printf (PRINT_DEVELOPER, "SINMDL_DrawModel: NULL oldanim handle in %s\n",
				currentmodel->name);
		oldanim = currentanim;
	}

	if (!currentanim)
		return;

	// get current frame
	if (currententity->frame >= currentanim->num_frames || currententity->frame < 0)
	{
		ri.Con_Printf (PRINT_DEVELOPER, "SINMDL_DrawModel: no such frame %d in anim %s for %s\n",
				currententity->frame, currentanim->name, currentmodel->name);
		currententity->frame %= currentanim->num_frames;
	}

	// get old frame
	if (currententity->oldframe >= oldanim->num_frames || currententity->oldframe < 0)
	{
		ri.Con_Printf (PRINT_DEVELOPER, "SINMDL_DrawModel: no such old frame %d in anim %s for %s\n",
				currententity->oldframe, oldanim->name, currentmodel->name);
		currententity->oldframe %= oldanim->num_frames;
	}
	framelerp = currententity->backlerp;
	//
	// calculate frame diff
	//
	if (currentanim == oldanim)
	{
		float delta;

		pframe = (dsintriframe_t *) ((byte *)currentanim + currentanim->ofs_frames);
		currentframe = &pframe[currententity->frame];
		delta = 0.1f / (float)currentframe->frametime;

		i = (currententity->frame - currententity->oldframe);
		if (i < 0)
			i += currentanim->num_frames;
		if (i > 1 && i == delta)
		{
			int offset;
			float base;

			base = (1.0f - currententity->backlerp) * i;
			offset = (int)base;
			currententity->oldframe += offset;
			currententity->oldframe %= currentanim->num_frames;
			currententity->frame = (currententity->oldframe + 1) % currentanim->num_frames;
			framelerp = 1.0f - (base - ((float)offset / (float)i));
		}
	}
	// point to the data
	pframe = (dsintriframe_t *) ((byte *)currentanim + currentanim->ofs_frames);
	currentframe = &pframe[currententity->frame];
	currentverts = (dtrivertx_t *) ((byte *)currentframe + currentframe->ofs_verts);

	// point to the data
	pframe = (dsintriframe_t *) ((byte *)oldanim + oldanim->ofs_frames);
	oldframe = &pframe[currententity->oldframe];
	oldverts = (dtrivertx_t *) ((byte *)oldframe + oldframe->ofs_verts);

	//
	// resolve rendering flags
	// grab the flags from the current entity and add the ones from the baseframe as well
	//
	envmapped =false;
	if (currententity->flags & RF_ENVMAPPED)
	{
		memset(rendergroups, MDL_GROUP_ENVMAPPED_SILVER, sizeof(rendergroups));
		envmapped = true;
	}
	else
	{
		memset(rendergroups, 0, sizeof(rendergroups));
	}
	pgroup = (dsingroup_t *) ((byte *)pmdl + pmdl->ofs_groups);
	for (i = 0; i < pmdl->num_groups; i++, pgroup++)
	{
		for (j = 0; j < pgroup->num_subgroups; j++)
		{
			int index;
			index = pgroup->subgroups[j].basegroupnum;
			if (index >= 0 && index < currentbase->num_groups)
			{
				rendergroups[index] |= pgroup->subgroups[j].flags;
				rendergroups[index] |= currententity->groups[i];
				if (rendergroups[index] & (MDL_GROUP_ENVMAPPED_SILVER|MDL_GROUP_ENVMAPPED_GOLD))
					envmapped = true;
			}
			else
			{
				ri.Con_Printf (PRINT_DEVELOPER, "SINMDL_DrawModel: invalid subgroup basegroup index\n");
			}
		}
	}

	if (envmapped)
	{
		vec3_t	forward,right,up;
		float	worldtrans[3][4];
		float	viewmatrix[3][4];

		//
		// calculate eye space transform
		//
		VectorCopy(e->mat[0], forward);
		VectorNegate(e->mat[1], right);
		VectorCopy(e->mat[2], up);

		memset(viewmatrix, 0, sizeof(viewmatrix));

		for (i = 0; i < 3; i++)
		{
			worldtrans[i][0] =  forward[i];
			worldtrans[i][1] = -right[i];
			worldtrans[i][2] =  up[i];
		}

		worldtrans[0][3] = e->origin[0]-r_origin[0];
		worldtrans[1][3] = e->origin[1]-r_origin[1];
		worldtrans[2][3] = e->origin[2]-r_origin[2];

		VectorCopy (vright, viewmatrix[0]);
		VectorNegate (vup, viewmatrix[1]);
		VectorCopy (vpn, viewmatrix[2]);

		R_ConcatTransforms (viewmatrix, worldtrans, sinmdl_transform);
	}


	if (!r_lerpmodels->value)
	{
		currententity->backlerp = 0;
		framelerp = 0;
	}
	SINMDL_DrawFrameLerp (pmdl, currententity->backlerp, framelerp);

	GL_TexEnv(GL_REPLACE);
	qglShadeModel (GL_FLAT);

	qglPopMatrix ();

	if (gl_bbox->intValue) /* Knightmare */
	{
		SINMDL_DrawBbox(bbox);
	}

	if (weapon_lefthanded)
	{
		qglMatrixMode(GL_PROJECTION);
		qglPopMatrix();
		qglMatrixMode(GL_MODELVIEW);
		qglCullFace(GL_FRONT);
	}

	if (currententity->flags & RF_TRANSLUCENT)
	{
		qglDepthMask (1);
		qglDisable (GL_BLEND);
	}

	if (currententity->flags & RF_WEAPONMODEL)
		qglDepthRange (gldepthmin, gldepthmax);

	if (gl_shadows->value && !(currententity->flags & (RF_TRANSLUCENT | RF_WEAPONMODEL)) && !(currententity->parent) && (e->flags & RF_XFLIP))
	{
		float pitch;
		qglPushMatrix ();
		pitch = e->angles[PITCH];	// sigh.
		e->angles[PITCH] = 0;
		R_RotateForEntity (e);
		e->angles[PITCH] = pitch;
		qglDisable (GL_TEXTURE_2D);
		qglEnable (GL_BLEND);
		if (gl_shadows->value == 1)
			SINMDL_DrawShadow (pmdl);
		else
			SINMDL_DrawShadow2 (pmdl);
		qglEnable (GL_TEXTURE_2D);
		qglDisable (GL_BLEND);
		qglPopMatrix ();
	}
	qglColor4f (1,1,1,1);
	/*
	if (envmapped && qglActiveTextureARB)
		GL_EnableMultitexture(false);
	*/
	//qglEnable (GL_DEPTH_TEST);
}
