//-----------------------------------------------------------------------------
//
//  $Logfile:: /Source/r_sinmdl.c                                             $
// $Revision:: 4                                                              $
//   $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:
// routines for setting up to draw SIN models
//
#include "r_local.h"
#include "r_sinmdl.h"

extern void R_ViewChanged( rvrect_t *vr );

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

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] = R_FindImage (pskins[i].path, it_skin, false);
	}
	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] = R_FindImage (pskins[i].path, it_skin, false);
	}

	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_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 LIGHT_MIN   5       // lowest light value we'll allow, to avoid the
//  need for inner-loop light clamping

static dsinbasemdl_t	*currentbase;
static dtrivertx_t	*currentverts;
static dtrivertx_t	*oldverts;
static dsinanim_t	*currentanim;
static dsinanim_t	*oldanim;
static dsintriframe_t	*currentframe;
static dsintriframe_t	*oldframe;
static dsinstvert_t	*stverts;
static float		currentscale;
static int		skinwidth;
static int		skinheight;
static qboolean		weapon_lefthanded;

// MED
static qboolean		weapon_fixedfov;
static float		weapon_storedfov_x;
static float		weapon_storedfov_y;

#define MAX_RENDER_GROUPS 32
static int rendergroups[MAX_RENDER_GROUPS];

extern float s_ziscale;

#define NUMVERTEXNORMALS    162
extern float r_avertexnormals[NUMVERTEXNORMALS][3];

extern vec3_t			r_plightvec;
extern vec3_t			r_lerped[1024];
extern vec3_t			r_lerp_frontv, r_lerp_backv, r_lerp_move;


extern float	aliastransform[3][4];
extern float	aliasworldtransform[3][4];
extern float	aliasoldworldtransform[3][4];

static int groupflags;


void R_AliasSetUpTransform (void);
void R_AliasTransformVector (vec3_t in, vec3_t out, float m[3][4]);
void R_AliasProjectAndClipTestFinalVert (finalvert_t *fv);
void R_AliasTransformFinalVerts (int numpoints, finalvert_t *fv, dtrivertx_t *oldv, dtrivertx_t *newv);
void R_AliasSetupLighting (void);

#define BBOX_TRIVIAL_ACCEPT 0
#define BBOX_MUST_CLIP_XY   1
#define BBOX_MUST_CLIP_Z    2
#define BBOX_TRIVIAL_REJECT 8

/*
** R_SINMDLCheckFrameBBox
**
** Checks a specific SINMDL frame bounding box
*/
static unsigned long R_SINMDLCheckFrameBBox (dsinmdl_t *pmdl, float worldxf[3][4])
{
	unsigned long aggregate_and_clipcode = ~0U,
	              aggregate_or_clipcode = 0;
	int i;
	vec3_t mins, maxs;
	vec3_t transformed_min, transformed_max;
	qboolean zclipped = false, zfullyclipped = true;

	/*
	** get the exact frame bounding box
	*/
	for (i = 0; i < 3; i++)
	{
		mins[i] = pmdl->mins[i]*currentscale*pmdl->load_scale;
		maxs[i] = pmdl->maxs[i]*currentscale*pmdl->load_scale;
	}

	/*
	** transform the min and max values into view space
	*/
	R_AliasTransformVector(mins, transformed_min, aliastransform);
	R_AliasTransformVector(maxs, transformed_max, aliastransform);

	if (transformed_min[2] >= ALIAS_Z_CLIP_PLANE)
		zfullyclipped = false;
	if (transformed_max[2] >= ALIAS_Z_CLIP_PLANE)
		zfullyclipped = false;

	if (zfullyclipped)
	{
		return BBOX_TRIVIAL_REJECT;
	}
	if (zclipped)
	{
		return (BBOX_MUST_CLIP_XY | BBOX_MUST_CLIP_Z);
	}

	/*
	** build a transformed bounding box from the given min and max
	*/
	for (i = 0; i < 8; i++)
	{
		int j;
		vec3_t tmp, transformed;
		unsigned long clipcode = 0;

		if (i & 1)
			tmp[0] = mins[0]*currentscale*pmdl->load_scale;
		else
			tmp[0] = maxs[0]*currentscale*pmdl->load_scale;

		if (i & 2)
			tmp[1] = mins[1]*currentscale*pmdl->load_scale;
		else
			tmp[1] = maxs[1]*currentscale*pmdl->load_scale;

		if (i & 4)
			tmp[2] = mins[2]*currentscale*pmdl->load_scale;
		else
			tmp[2] = maxs[2]*currentscale*pmdl->load_scale;

		R_AliasTransformVector(tmp, transformed, worldxf);

		for (j = 0; j < 4; j++)
		{
			float dp = DotProduct(transformed, view_clipplanes[j].normal);

			if ((dp - view_clipplanes[j].dist) < 0.0F)
				clipcode |= 1 << j;
		}

		aggregate_and_clipcode &= clipcode;
		aggregate_or_clipcode  |= clipcode;
	}

	if (aggregate_and_clipcode)
	{
		return BBOX_TRIVIAL_REJECT;
	}
	if (!aggregate_or_clipcode)
	{
		return BBOX_TRIVIAL_ACCEPT;
	}

	return BBOX_MUST_CLIP_XY;
}

static unsigned long R_SINMDLCheckBBox (dsinmdl_t *pmdl)
{
	unsigned long ccode;

	ccode = R_SINMDLCheckFrameBBox(pmdl, aliasworldtransform);

	if (ccode == BBOX_TRIVIAL_ACCEPT)
		return BBOX_TRIVIAL_ACCEPT;
	else if (ccode & BBOX_TRIVIAL_REJECT)
		return BBOX_TRIVIAL_REJECT;
	else
		return (ccode & ~BBOX_TRIVIAL_REJECT);
}


/*
================
R_SINMDLPreparePoints

General clipped case
================
*/
static void R_SINMDLPreparePoints (dsintrigroup_t *pgroup, finalvert_t *pfinalverts)
{
	int			i;
	finalvert_t	*pfv[3];
	dsintriangle_t	*ptri;
	float	frontlerp, backlerp;

	backlerp = currententity->backlerp;
	frontlerp = 1.0f - backlerp;

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

	//
	// clip and draw all triangles
	//
	if (weapon_lefthanded)
	{
		for (i = 0; i < pgroup->num_tris; i++, ptri++)
		{
			pfv[0] = &pfinalverts[ptri->index_xyz[0]];
			pfv[1] = &pfinalverts[ptri->index_xyz[1]];
			pfv[2] = &pfinalverts[ptri->index_xyz[2]];

			if (pfv[0]->flags & pfv[1]->flags & pfv[2]->flags)
				continue;		// completely clipped

			if (groupflags & (MDL_GROUP_ENVMAPPED_SILVER|MDL_GROUP_ENVMAPPED_GOLD))
			{
				int j, k;
				vec3_t eye_normal;
				vec3_t eye_vert;
				vec3_t normal;
				float two_nu, m, fx, fy, fz;
				dtrivertx_t *vert;
				dtrivertx_t *oldvert;
				for (j = 0; j < 3; j++)
				{
					vert = &currentverts[ptri->index_xyz[j]];
					oldvert = &oldverts[ptri->index_xyz[j]];
					for (k = 0; k < 3; k++)
						normal[k] = (r_avertexnormals[vert->lightnormalindex][k]*frontlerp)+
							    (r_avertexnormals[oldvert->lightnormalindex][k]*backlerp);

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

					VectorCopy(pfv[j]->xyz, eye_vert);
					VectorNormalize(eye_vert);
					two_nu = 1.98f * 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 = 1.98F * sqrt(fx*fx + fy*fy + (fz+1.0F)*(fz+1.0F));
					if (m == 0.0F)
					{
						pfv[j]->s = (skinwidth>>1)<<16;
						pfv[j]->t = (skinheight>>1)<<16;
					}
					else
					{
						float mInv = 1.0F / m;
						pfv[j]->s = (int)((fx * mInv + 0.5f) * skinwidth)<<16;
						pfv[j]->t = (int)((fy * mInv + 0.5f) * skinheight)<<16;
					}
				}
			}
			else
			{
				// insert s/t coordinates
				pfv[0]->s = (int)(stverts[ptri->index_st[0]].s*skinwidth) << 16;
				pfv[0]->t = (int)(stverts[ptri->index_st[0]].t*skinheight) << 16;

				pfv[1]->s = (int)(stverts[ptri->index_st[1]].s*skinwidth) << 16;
				pfv[1]->t = (int)(stverts[ptri->index_st[1]].t*skinheight) << 16;

				pfv[2]->s = (int)(stverts[ptri->index_st[2]].s*skinwidth) << 16;
				pfv[2]->t = (int)(stverts[ptri->index_st[2]].t*skinheight) << 16;
			}

			if (groupflags & MDL_GROUP_FULLBRIGHT)
			{
				pfv[0]->l = 0;
				pfv[1]->l = 0;
				pfv[2]->l = 0;
			}

			if (!(pfv[0]->flags | pfv[1]->flags | pfv[2]->flags))
			{	// totally unclipped
				aliastriangleparms.a = pfv[2];
				aliastriangleparms.b = pfv[1];
				aliastriangleparms.c = pfv[0];

				R_DrawTriangle(groupflags & MDL_GROUP_TWOSIDED);
			}
			else
			{
				R_AliasClipTriangle ( pfv[2], pfv[1], pfv[0], (groupflags & MDL_GROUP_TWOSIDED));
			}
		}
	}
	else
	{
		for (i=0; i<pgroup->num_tris; i++, ptri++)
		{
			pfv[0] = &pfinalverts[ptri->index_xyz[0]];
			pfv[1] = &pfinalverts[ptri->index_xyz[1]];
			pfv[2] = &pfinalverts[ptri->index_xyz[2]];

			if (pfv[0]->flags & pfv[1]->flags & pfv[2]->flags)
				continue;		// completely clipped

			if (groupflags & (MDL_GROUP_ENVMAPPED_SILVER|MDL_GROUP_ENVMAPPED_GOLD))
			{
				int j, k;
				vec3_t normal;
				vec3_t eye_normal;
				vec3_t eye_vert;
				float two_nu, m, fx, fy, fz;
				dtrivertx_t *vert;
				dtrivertx_t *oldvert;
				for (j = 0; j < 3; j++)
				{
					vert = &currentverts[ptri->index_xyz[j]];
					oldvert = &oldverts[ptri->index_xyz[j]];
					for (k = 0; k < 3; k++)
						normal[k] = (r_avertexnormals[vert->lightnormalindex][k]*frontlerp)+
							    (r_avertexnormals[oldvert->lightnormalindex][k]*backlerp);

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

					VectorCopy(pfv[j]->xyz, eye_vert);
					VectorNormalize(eye_vert);
					two_nu = 1.98F * 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 = 1.98F * sqrt(fx*fx + fy*fy + (fz+1.0F)*(fz+1.0F));
					if (m == 0.0F)
					{
						pfv[j]->s = (skinwidth>>1)<<16;
						pfv[j]->t = (skinheight>>1)<<16;
					}
					else
					{
						float mInv = 1.0F / m;
						pfv[j]->s = (int)((fx * mInv + 0.5f) * skinwidth)<<16;
						pfv[j]->t = (int)((fy * mInv + 0.5f) * skinheight)<<16;
					}
				}
			}
			else
			{
				// insert s/t coordinates
				pfv[0]->s = (int)(stverts[ptri->index_st[0]].s*skinwidth) << 16;
				pfv[0]->t = (int)(stverts[ptri->index_st[0]].t*skinheight) << 16;

				pfv[1]->s = (int)(stverts[ptri->index_st[1]].s*skinwidth) << 16;
				pfv[1]->t = (int)(stverts[ptri->index_st[1]].t*skinheight) << 16;

				pfv[2]->s = (int)(stverts[ptri->index_st[2]].s*skinwidth) << 16;
				pfv[2]->t = (int)(stverts[ptri->index_st[2]].t*skinheight) << 16;
			}

			if (groupflags & MDL_GROUP_FULLBRIGHT)
			{
				pfv[0]->l = 0;
				pfv[1]->l = 0;
				pfv[2]->l = 0;
			}

			if (!(pfv[0]->flags | pfv[1]->flags | pfv[2]->flags))
			{	// totally unclipped
				aliastriangleparms.a = pfv[0];
				aliastriangleparms.b = pfv[1];
				aliastriangleparms.c = pfv[2];

				R_DrawTriangle(groupflags & MDL_GROUP_TWOSIDED);
			}
			else
			{	// partially clipped
				R_AliasClipTriangle (pfv[0], pfv[1], pfv[2], (groupflags & MDL_GROUP_TWOSIDED));
			}
		}
	}
}

/*
===============
R_SINDMLSetupSkin
===============
*/
static qboolean R_SINMDLSetupSkin (dsinmdl_t *pmdl, int skinnum)
{
	image_t			*pskindesc;

	if (groupflags & MDL_GROUP_ENVMAPPED_SILVER)
	{
		// 2015 code
		if(game2015->value)
		{
			if (currententity->lightofs && !(currententity->flags & RF_LIGHTOFFSET))
				pskindesc = r_gogglesmap;
			else
				pskindesc = r_envmap;
		}
		else
		{
			pskindesc = r_envmap;
		}
	}
	else if (groupflags & MDL_GROUP_ENVMAPPED_GOLD)
	{
		pskindesc = r_goldenvmap;
	}
	else if (currententity->skin)
	{
		// custom player skin
		pskindesc = currententity->skin;
		// Check if this is a damage skin
		if ((groupflags & 0x3) == 1)
		{
			pskindesc = currententity->damageskin;
		}
	}
	else
	{
		skinnum = currententity->skinnum + (groupflags&0x3);
		if (skinnum >= pmdl->num_skins || skinnum < 0)
		{
			ri.Con_Printf ( PRINT_DEVELOPER, "R_AliasSetupSkin: no such skin # %d\n", skinnum );
			skinnum = 0;
		}

		pskindesc = currentmodel->skins[skinnum];
	}

	if (!pskindesc || !pskindesc->pixels[0])
		return false;

	r_affinetridesc.pskin = pskindesc->pixels[0];
	r_affinetridesc.skinwidth = pskindesc->width;
	r_affinetridesc.skinheight = pskindesc->height;
	skinwidth = pskindesc->width;
	skinheight = pskindesc->height;
	if (vid.bitdepth > 8) {
		r_affinetridesc.pskin = R_SetAliasColormap(pskindesc, d_drawopaque);
	}
	r_affinetridesc.pskin_max = ((byte *)r_affinetridesc.pskin + (r_affinetridesc.skinwidth * r_affinetridesc.skinheight) - 1);

	if (!r_affinetridesc.pskin)
		return false;

	R_PolysetUpdateTables ();       // FIXME: precalc edge lookups

	return true;
}


/*
=================
R_SINMDLSetupFrames

=================
*/
static float R_SINMDLSetupFrames (dsinmdl_t *pmdl)
{
	int i, j;
	float framelerp;
	dsinanimdef_t *panimdef;
	dsintriframe_t *pframe;
	dsingroup_t *pgroup;

	framelerp = currententity->backlerp;
	// 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 framelerp;
	}

	// get the stverts
	stverts = (dsinstvert_t *) ((byte *)currentbase + currentbase->ofs_st);

	// 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;
	}

	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;
	}

	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 framelerp;

	// 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;
	}

	//
	// 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
	//
	if (currententity->flags & RF_ENVMAPPED)
		memset(rendergroups, MDL_GROUP_ENVMAPPED_SILVER, sizeof(rendergroups));
	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;
			rendergroups[index] |= pgroup->subgroups[j].flags;
			rendergroups[index] |= currententity->groups[i];
		}
	}
	return framelerp;
}

/*
** R_SINMDLSetUpLerpData
**
** Precomputes lerp coefficients used for the whole frame.
*/
static void R_SINMDLSetUpLerpData (dsinmdl_t *pmdl, float backlerp, float framebacklerp)
{
//	float	frontlerp;
	float	framefrontlerp;
	vec3_t	translation;
	int		i;

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

	/*
	** translation is the vector from last position to this position
	*/
	VectorSubtract ( currententity->oldorigin, currententity->origin, translation );

	/*
	** move should be the delta back to the previous frame * backlerp
	*/
	r_lerp_move[0] =  DotProduct(translation, currententity->mat[0]);	// forward
	r_lerp_move[1] =  DotProduct(translation, currententity->mat[1]);	// left
	r_lerp_move[2] =  DotProduct(translation, currententity->mat[2]);	// up

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

	for (i = 0; i < 3; i++)
	{
		r_lerp_move[i] = framebacklerp*translation[i] + 
				 backlerp*r_lerp_move[i] + 
				 ((framefrontlerp*currentframe->translate[i])+pmdl->load_origin[i])*currentscale*pmdl->load_scale;
		r_lerp_frontv[i] = framefrontlerp*currentframe->scale[i]*currentscale*pmdl->load_scale;
		r_lerp_backv[i] = framebacklerp*oldframe->scale[i]*currentscale*pmdl->load_scale;
	}
}

static finalvert_t finalverts[MAXALIASVERTS + ((CACHE_SIZE - 1) / sizeof(finalvert_t)) + 3];
/*
================
R_SINMDLDrawModel
================
*/
void R_SINMDLDrawModel( void )
{
	int				i;
	float			framelerp;
	dsinmdl_t	*pmdl;
	finalvert_t	*pfinalverts;

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

	pmdl = ri.SINMDL_GetDef((int)(intptr_t)currentmodel->extradata);

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

	if (r_lerpmodels->value == 0)
		currententity->backlerp = 0;

	//
	// determine whether the weapon is right handed or left handed
	//
	weapon_lefthanded = false;
	// 2015 code
	weapon_fixedfov = false;
	if (currententity->flags & RF_WEAPONMODEL)
	{
		// 2015 code
		if (r_lefthand->value == 2.0F)
			return;
		if (r_lefthand->value == 1.0F)
			weapon_lefthanded = true;
		if (currententity->flags & RF_XFLIP)
			weapon_lefthanded ^= true;

		// 2015 code
		if (r_newrefdef.fov_x > 90)
		{
			weapon_fixedfov = true;
			weapon_storedfov_x = r_newrefdef.fov_x;
			weapon_storedfov_y = r_newrefdef.fov_y;
			r_newrefdef.fov_x = 90;
			r_newrefdef.fov_y = 75;
			R_ViewChanged(&r_refdef.vrect);
		}

		if (weapon_lefthanded)
			aliasxscale = -aliasxscale;
	}

	/*
	** we have to set our frame pointers and transformations before
	** doing any real work
	*/
	R_AliasSetUpTransform();

	// see if the bounding box lets us trivially reject, also sets
	// trivial accept status
	if (R_SINMDLCheckBBox(pmdl) == BBOX_TRIVIAL_REJECT)
	{
		if (weapon_lefthanded)
		{
			aliasxscale = -aliasxscale;
		}
		// 2015 code
		if (weapon_fixedfov)
		{
			weapon_fixedfov = false;
			r_newrefdef.fov_x = weapon_storedfov_x;
			r_newrefdef.fov_y = weapon_storedfov_y;
			R_ViewChanged(&r_refdef.vrect);
		}
		return;
	}

	framelerp = R_SINMDLSetupFrames(pmdl);

	r_amodels_drawn++;
	R_AliasSetupLighting ();

	/*
	** compute this_frame and old_frame addresses
	*/
	R_SINMDLSetUpLerpData(pmdl, currententity->backlerp, framelerp);

	if (currententity->flags & RF_WEAPONMODEL)
		s_ziscale = (float)0x8000 * (float)0x10000 * 6.0;
	else
		s_ziscale = (float)0x8000 * (float)0x10000;

	// cache align vertexes properly
	pfinalverts = (finalvert_t *)
			(((intptr_t)&finalverts[0] + CACHE_SIZE - 1) & ~(CACHE_SIZE - 1));

	R_AliasTransformFinalVerts(currentbase->num_xyz, pfinalverts, oldverts, currentverts);

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

		pgroup = &currentbase->groups[i];
		groupflags = rendergroups[i];

		if (rendergroups[i] & MDL_GROUP_NODRAW)
			continue;

		/*
		** select the proper span routine based on translucency
		*/
		d_drawopaque = false;
		if (vid.bitdepth > 8)
		{
			if (currententity->flags & RF_TRANSLUCENT)
			{
				alias_alphalevel = currententity->alpha * NUMALPHALEVELS;
				if (alias_alphalevel >= NUMALPHALEVELS)
					alias_alphalevel = NUMALPHALEVELS - 1;
				d_pdrawspans = R_PolysetDrawSpans8Alpha_16bit;
			}
			else
			{
				if (rendergroups[i] & MDL_GROUP_TRANSLUCENT_33)
				{
					// 2015 code
					if (rendergroups[i] & MDL_GROUP_TRANSLUCENT_66)
					{
						alias_alphalevel = currententity->alpha * NUMALPHALEVELS;
						if (alias_alphalevel >= NUMALPHALEVELS)
							alias_alphalevel = NUMALPHALEVELS - 1;
						d_pdrawspans = R_PolysetDrawSpans8Alpha_16bit;
					}
					else
					{
						alias_alphalevel = 0.66f * NUMALPHALEVELS;
						d_pdrawspans = R_PolysetDrawSpans8Alpha_16bit;
					}
				}
				else if (rendergroups[i] & MDL_GROUP_TRANSLUCENT_66)
				{
					alias_alphalevel = 0.33f * NUMALPHALEVELS;
					d_pdrawspans = R_PolysetDrawSpans8Alpha_16bit;
				}
				else
				{
					if (rendergroups[i] & MDL_GROUP_MASKED)
						d_pdrawspans = R_PolysetDrawSpans8_Masked_16bit;
					else
					{
						d_drawopaque = true;
						d_pdrawspans = R_PolysetDrawSpans8_Opaque_16bit;
					}
				}
			}
		}
		else
		{
			if (currententity->flags & RF_TRANSLUCENT)
			{
				if (currententity->alpha > 0.33)
					d_pdrawspans = R_PolysetDrawSpans8_66;
				else
					d_pdrawspans = R_PolysetDrawSpans8_33;
			}
			else
			{
				if (rendergroups[i] & MDL_GROUP_TRANSLUCENT_33)
				{
					// 2015 code
					if (rendergroups[i] & MDL_GROUP_TRANSLUCENT_66)
					{
						if (currententity->alpha > 0.33)
							d_pdrawspans = R_PolysetDrawSpans8_66;
						else
							d_pdrawspans = R_PolysetDrawSpans8_33;
					}
					else
						d_pdrawspans = R_PolysetDrawSpans8_33;
				}
				else if (rendergroups[i] & MDL_GROUP_TRANSLUCENT_66)
					d_pdrawspans = R_PolysetDrawSpans8_66;
				else
				{
					if (rendergroups[i] & MDL_GROUP_MASKED)
						d_pdrawspans = R_PolysetDrawSpans8_Masked;
					else
					{
						d_drawopaque = true;
						d_pdrawspans = R_PolysetDrawSpans8_Opaque;
					}
				}
			}
		}

		// set up the skin and verify it exists
		if (!R_SINMDLSetupSkin(pmdl, currententity->skinnum + (rendergroups[i]&0x3)))
		{
			ri.Con_Printf(PRINT_DEVELOPER, "R_AliasDrawModel - NULL skin found\n");
			return;
		}

		R_SINMDLPreparePoints (pgroup, pfinalverts);
	}

	if (weapon_lefthanded)
	{
		aliasxscale = -aliasxscale;
	}

	// 2015 code
	if (weapon_fixedfov)
	{
		weapon_fixedfov = false;
		r_newrefdef.fov_x = weapon_storedfov_x;
		r_newrefdef.fov_y = weapon_storedfov_y;
		R_ViewChanged(&r_refdef.vrect);
	}
}

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