/* gl_md3.c 
 * Based on code from the Aftershock 3D rendering engine 
 * Copyright (C) 1999 Stephen C. Taylor 
 * 
 * This program is free software; you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation; either version 2 of the License, or 
 * (at your option) any later version. 
 * 
 * This program is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 * GNU General Public License for more details. 
 * 
 * You should have received a copy of the GNU General Public License 
 * along with this program; if not, write to the Free Software 
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 
 */
//gl_md3.c "dont mess" lethal!!!

#include "quakedef.h" 

qboolean	r_md3fullbright_draw;

// precalculated dot products for quantized angles
float	r_avertexnormal_dots_md3[16][256] =
#include "anorm_dots.h"
;

float	*shadedots_md3	= r_avertexnormal_dots_md3[0];
float	*shadedots2_md3	= r_avertexnormal_dots_md3[0];

// make these seperate "works"
// only lightcolor is shared but thats k
float	shadelightmd3, ambientlightmd3;
float	apitchmd3, ayawmd3;
float   lightlerpoffsetmd3;

/*
=============
GL_CalcQ3LightLerp

Calculates lightning between two angles
=============
*/
float GL_CalcQ3LightLerp (float l1, float l2)
{
	float l, diff;

	if (l1 != l2) {
		if (l1 > l2) {
			diff = (l1 - l2) * -1;
		} else {
			diff = l2 - l1;
		}
		diff *= lightlerpoffsetmd3;
		l = l1 + diff;
	} else {
		l = l1;
	}
	// add contrast - this is a nasty hack from Pox's tut which simulates specular lighting
	// mainly introduced cos at the time he didn't know the norms.  however, my engine does
	// calculate the norms at model load time (although it doesn't really use them) so i
	// should be able to do proper specular lighting... if only i knew how :-(
	l *= l;

	return l;
}

/*
=================
GL_DrawQ3AliasFrame

Please fix this ANYONE!!!
=================
*/
void GL_DrawQ3AliasFrame (md3header_mem_t *header, int lastpose, int pose, float blend, qboolean mtex)
{
	int					i, j, k;
	int					frame;
	int					lastframe;
	int					vertframeoffset;
	int					lastvertframeoffset;
	vec3_t				l_v;
	float				alpha, iblend;
	float				d1, d2, l, l1, l2;
	md3frame_mem_t		*light, *lastlight;
	md3surface_mem_t	*surf;
	md3st_mem_t			*tc;
	md3tri_mem_t		*tris;
	md3vert_mem_t		*verts, *vertslast;
	md3trivertx_t		*lightverts, *lastlightverts;

	alpha = (currententity == &cl.viewent && gl_mtexable) ? ((cl.items & IT_INVISIBILITY) ? gl_ringalpha.value : bound(0, r_drawviewmodel.value, 1)) : model_alpha;

	iblend = 1.0 - blend;

	surf = (md3surface_mem_t *)((byte *)header + header->offs_surfaces);

	if (alpha < 1.0) {
		glEnable (GL_BLEND);
	}

	for (i = 0; i < header->num_surfaces; i++) {
		if (*(long *)surf->id != MD3IDHEADER) {
			Con_Printf("MD3 bad surface for: %s\n", header->filename);
		}

		if (surf->num_surf_frames == 0){
			surf = (md3surface_mem_t *)((byte *)surf + surf->offs_end);
			continue;	//shouldn't ever do this, each surface should have at least one frame
		}
		frame = pose%surf->num_surf_frames;	//cap the frame inside the list of frames in the model
		vertframeoffset = frame*surf->num_surf_verts * sizeof(md3vert_mem_t);
		lastframe = lastpose%surf->num_surf_frames;
		lastvertframeoffset = lastframe*surf->num_surf_verts * sizeof(md3vert_mem_t);

		tc = (md3st_mem_t *)((byte *)surf + surf->offs_tc);
		tris = (md3tri_mem_t *)((byte *)surf + surf->offs_tris);
		verts = (md3vert_mem_t *)((byte *)surf + surf->offs_verts + vertframeoffset);
		vertslast = (md3vert_mem_t *)((byte *)surf + surf->offs_verts + lastvertframeoffset);

		// now this gets hackish
		light = (md3frame_mem_t *)((int) header + header->offs_frames + (header->num_frames * lastpose)); 
		lastlight = (md3frame_mem_t *)((int) header + header->offs_frames + (header->num_frames * pose)); 

		// damn this was a bitch to get going !!
		lightverts = &light->verts[0];
		lastlightverts = &lastlight->verts[0];

		// normals and vertexes come from the frame list
		// blend the light intensity from the two frames together
		if (gl_vertexlights.value && !r_md3fullbright_draw) {
			l = R_LerpVertexLight (lightverts->lightnormalindex, lastlightverts->lightnormalindex, blend, apitchmd3, ayawmd3);

			l = min(l, 1);	
		} else {
			d1 = (shadedots_md3[lastlightverts->lightnormalindex] * shadelightmd3 + ambientlightmd3) - (shadedots2_md3[lightverts->lightnormalindex] * shadelightmd3 + ambientlightmd3);
			d2 = (shadedots_md3[lastlightverts->lightnormalindex] * shadelightmd3 + ambientlightmd3) - (shadedots2_md3[lightverts->lightnormalindex] * shadelightmd3 + ambientlightmd3);			

			l1 = min(1, (shadedots_md3[lightverts->lightnormalindex] * shadelightmd3 + ambientlightmd3) + (blend * d1) / 256);
			l2 = min(1, (shadedots_md3[lightverts->lightnormalindex] * shadelightmd3 + ambientlightmd3) + (blend * d2) / 256);

			l = GL_CalcQ3LightLerp (l1, l2);
		}

		if (!r_md3fullbright_draw) {
			for (i=0 ; i<3 ; i++) {
				l_v[i] = lightcolor[i] / 256 + l;
			}
			glColor4f (l_v[0], l_v[1], l_v[2], alpha);
		} else {
			glColor4f (l, l, l, alpha);
		}

		if (blend >=1) {
			glNormalPointer(GL_FLOAT, 6 * sizeof(float), (float *)verts->normal);
			glEnableClientState(GL_NORMAL_ARRAY);

			glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (float *)verts->vec);
			glEnableClientState(GL_VERTEX_ARRAY);

			glTexCoordPointer(2, GL_FLOAT, 0, (float *)tc);
			glEnableClientState(GL_TEXTURE_COORD_ARRAY);

			glDrawElements(GL_TRIANGLES, surf->num_surf_tris*3, GL_UNSIGNED_INT, (int *)tris);

			glDisableClientState(GL_NORMAL_ARRAY);
			glDisableClientState(GL_VERTEX_ARRAY);
			glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		} else {
			glBegin(GL_TRIANGLES);
			//for each triangle
			for (j = 0; j < surf->num_surf_tris; j++) {
				//draw the poly
				for (k=0; k < 3; k++) {
					//interpolated
					if(mtex) {
						qglMultiTexCoord2f (GL_TEXTURE0_ARB,tc[tris[j].tri[k]].s, tc[tris[j].tri[k]].t);
						qglMultiTexCoord2f (GL_TEXTURE1_ARB,tc[tris[j].tri[k]].s, tc[tris[j].tri[k]].t);
					} else {
						glTexCoord2f (tc[tris[j].tri[k]].s, tc[tris[j].tri[k]].t);
					}

					// Faster than the former struct
					glVertex3f(verts[tris[j].tri[k]].vec[0] * blend + vertslast[tris[j].tri[k]].vec[0] * iblend, 
							   verts[tris[j].tri[k]].vec[1] * blend + vertslast[tris[j].tri[k]].vec[1] * iblend, 
							   verts[tris[j].tri[k]].vec[2] * blend + vertslast[tris[j].tri[k]].vec[2] * iblend);

					// Faster than the former struct
					glNormal3f(verts[tris[j].tri[k]].normal[0] * blend + vertslast[tris[j].tri[k]].normal[0] * iblend, 
							   verts[tris[j].tri[k]].normal[1] * blend + vertslast[tris[j].tri[k]].normal[1] * iblend, 
							   verts[tris[j].tri[k]].normal[2] * blend + vertslast[tris[j].tri[k]].normal[2] * iblend);
				}
			}
		}
		glEnd(); // crap i mounted this in the wrong spot before causing all sorts off strange behaviour
	}
	surf = (md3surface_mem_t *)((byte *)surf + surf->offs_end);

	if (alpha < 1.0) {
		glDisable (GL_BLEND);
	}
	glColor3f (1, 1, 1);
}

/*
=================
R_SetupQ3AliasFrame

From TQ148 thx Tomaz
=================
*/
void R_SetupQ3AliasFrame (int frame, md3header_mem_t *header, entity_t *e, qboolean mtex)
{
	float	blend;

	// im getting confirms on non existing frames here
	// but dunno if its a bug with the md3 models, so far
	// it only seems to adhere to the rocketlauncher ?
	if ((frame >= header->num_frames) || (frame < 0)) {
		Con_DPrintf ("R_SetupQ3AliasFrame: no such frame %d\n", frame);
		frame = 0;
	}

	//md3 interpolation
	if (e->draw_lastmodel == e->model) {
		if (frame != e->draw_pose) {
			e->draw_lastpose = e->draw_pose;
			e->draw_pose = frame;
			e->draw_lerpstart = cl.time;
			blend = 0;
		} else {
			blend = (cl.time - e->draw_lerpstart) * 10.0;
		}
	} else { 
		// uninitialized
		e->draw_lastmodel = e->model;
		e->draw_lastpose = e->draw_pose = frame;
		e->draw_lerpstart = cl.time;
		blend = 0;
	}

	if (blend > 1) {
		blend = 1;
	}
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	GL_DrawQ3AliasFrame(header, e->draw_lastpose, frame, blend, mtex);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
}

/*
=================
R_DrawQ3Model

Modified Dr Labmans 
md3 code splitting it 
up to make it easier
to add dynamic lighting code
=================
*/
void R_DrawQ3Model(entity_t *e)
{
	md3header_mem_t		*header;
	md3shader_mem_t		*shader;
	md3surface_mem_t	*surf;
	model_t				*clmodel;
	vec3_t				dist, ang, dlight_color;
	vec3_t				vertexlightmd3;
	float				add, ang_ceil, ang_floor, radiusmax = 0.0;
	int					shaders, i, lnum;

	if (!gl_notrans.value) { 
		// allways true if not -nehahra
		model_alpha = currententity->transparency;
		if (model_alpha == 0) {
			model_alpha = 1.0;
		}
	} else {
		model_alpha = 1.0;
	}
	clmodel = e->model;

	// get lighting information
	// make thunderbolt and torches full light
	if (clmodel->mflags & MOD_BOLT) {
		ambientlightmd3 = 210;
		shadelightmd3 = 0;
		r_md3fullbright_draw = true;
	} else if (clmodel->mflags & MOD_FLAME) {
		ambientlightmd3 = 255;
		shadelightmd3 = 0;
		r_md3fullbright_draw = true;
	} else {	
		// normal lighting
		r_md3fullbright_draw = false;
		ambientlightmd3 = shadelightmd3 = R_LightPoint (e->origin);
		for (lnum = 0 ; lnum < MAX_DLIGHTS ; lnum++) {
			if (cl_dlights[lnum].die < cl.time || !cl_dlights[lnum].radius)	continue;
			VectorSubtract (e->origin, cl_dlights[lnum].origin, dist);
			add = cl_dlights[lnum].radius - VectorLength (dist);
			if (add > 0) {
				if (gl_vertexlights.value) {
					if (!radiusmax || cl_dlights[lnum].radius > radiusmax) {
						radiusmax = cl_dlights[lnum].radius;
						VectorCopy(cl_dlights[lnum].origin, vertexlightmd3);
					}
				}
				VectorCopy(bubblecolor[cl_dlights[lnum].type], dlight_color);
				for (i=0 ; i<3 ; i++) {
					lightcolor[i] = lightcolor[i] + (dlight_color[i] * add) * 2;
					if (lightcolor[i] > 256) {
						switch (i) {
						case 0:
							lightcolor[1] = lightcolor[1] - (1 * lightcolor[1]/3); 
							lightcolor[2] = lightcolor[2] - (1 * lightcolor[2]/3); 
							break;

						case 1:
							lightcolor[0] = lightcolor[0] - (1 * lightcolor[0]/3); 
							lightcolor[2] = lightcolor[2] - (1 * lightcolor[2]/3); 
							break;

						case 2:
							lightcolor[1] = lightcolor[1] - (1 * lightcolor[1]/3); 
							lightcolor[0] = lightcolor[0] - (1 * lightcolor[0]/3); 
							break;
						}
					}
				}
			}
		}

		// calculate pitch and yaw for vertex lighting
		if (gl_vertexlights.value) {
			apitchmd3 = e->angles[0];
			ayawmd3 = e->angles[1];
			if (!radiusmax) {
				vlight_pitch = 45;
				vlight_yaw = 45;
			} else {
				VectorSubtract(vertexlightmd3, e->origin, dist);
				vectoangles(dist, ang);
				vlight_pitch = ang[0];
				vlight_yaw = ang[1];
			}
		}

		// clamp lighting so it doesn't overbright as much
		ambientlightmd3 = min(128, ambientlightmd3);

		if (ambientlightmd3 + shadelightmd3 > 192) {
			shadelightmd3 = 192 - ambientlightmd3;
		}

		// always give the gun some light
		if (e == &cl.viewent && ambientlightmd3 < 24) {
			ambientlightmd3 = shadelightmd3 = 24;
		}

		// never allow players to go totally black
		if (clmodel->mflags & MOD_PLAYER) {
			if (ambientlightmd3 < 8) {
				ambientlightmd3 = shadelightmd3 = 8;
				r_md3fullbright_draw = false;
			}
		}
	}

	if (r_fullbrightskins.value && (clmodel->mflags & MOD_PLAYER)) {
		ambientlightmd3 = shadelightmd3 = 128;
		r_md3fullbright_draw = true;
	}

	if (gl_vertexlights.value) {
		// YUCK YUCK YUCK
		shadedots_md3= r_avertexnormal_dots_md3[((int)(e->angles[1])) & (16 - 1)];
	} else {
		// add pitch angle so lighting changes when looking up/down (mainly for viewmodel)
		lightlerpoffsetmd3 = e->angles[1] * (16 / 360.0);

		ang_ceil = ceil(lightlerpoffsetmd3);
		ang_floor = floor(lightlerpoffsetmd3);

		lightlerpoffsetmd3 = ang_ceil - lightlerpoffsetmd3;

		// it gives me the crepaas !!
		shadedots_md3 = r_avertexnormal_dots_md3[(int)ang_ceil & (16 - 1)];
		shadedots2_md3	= r_avertexnormal_dots_md3[(int)ang_floor & (16 - 1)];

		if (e->lastShadeLight) {
			shadelightmd3 = (shadelightmd3 + e->lastShadeLight) / 2;
		}

		if (shadelightmd3) {
			e->lastShadeLight = shadelightmd3;
		} else {
			e->lastShadeLight = 1;
		}
	}

	// Allocate the header
	header = (md3header_mem_t *)Mod_Extradata (e->model);

	// locate the proper data "huh surface related"!
	surf = (md3surface_mem_t *)((byte *)header + header->offs_surfaces);

	c_alias_polys += surf->num_surf_tris;

	//get pointer to shaders
	shader = (md3shader_mem_t *)((byte *)surf + surf->offs_shaders);

	shaders = shader[(currententity->skinnum%surf->num_surf_shaders)].texnum;

	if (shaders!=0) {
		GL_Bind(shaders);
	} else {
		GL_Bind(0);
	}

	if (*(long *)header->id != MD3IDHEADER){
		Con_Printf("MD3 bad model for: %s\n", header->filename);
		return;
	}
	glPushMatrix();

	//interpolate unless its the viewmodel
	if (gl_interpolate_animation.value && e != &cl.viewent) {
		R_BlendedRotateForEntity (e, 0);
	} else {
		R_RotateForEntity (e);
	}

	if (gl_smoothmodels.value) {
		glShadeModel (GL_SMOOTH);
	}

	if (gl_affinemodels.value) {
		glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);
	}

	if (gl_mtexable) {
		R_SetupQ3AliasFrame (e->frame, header, e, true);
	} else {
		R_SetupQ3AliasFrame (e->frame, header, e, false);
	}
	glShadeModel (GL_FLAT);

	if (gl_affinemodels.value) {
		glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	}
	glPopMatrix();
}

int	md3bboxmins[3], md3bboxmaxs[3];

/*
=================
Mod_LoadQ3Model
=================
*/
void Mod_LoadQ3Model(model_t *mod, void *buffer)
{
	md3header_t			*header;
	md3header_mem_t		*mem_head;
	md3surface_t		*surf;
	md3surface_mem_t	*currentsurf;
	md3shader_mem_t		*shader;
	md3frame_t			*framein;
	md3frame_mem_t		*frameout;
	int					i, j, picmip_flag;
	int					posn;
	int					surfstartposn;
	char				name[128];

	//we want to work out the size of the model in memory
	int					surf_size = 0;
	int					mem_size = 0;

	picmip_flag = gl_picmip_all.value ? TEX_MIPMAP : 0;

	//pointer to header
	header = (md3header_t *)buffer;

	//pointer to the surface list
	surf = (md3surface_t *)((byte *)buffer + header->surface_offs);

	for (i = 0; i < header->num_surfaces; i++)
	{
		surf_size += sizeof(md3surface_mem_t);
		surf_size += surf->num_surf_shaders * sizeof(md3shader_mem_t);
		surf_size += surf->num_surf_tris * sizeof(md3tri_mem_t);
		surf_size += surf->num_surf_verts * sizeof(md3st_mem_t);
		surf_size += surf->num_surf_verts * surf->num_surf_frames * sizeof(md3vert_mem_t);

		//goto next surf
		surf = (md3surface_t *)((byte *)surf + surf->end_offs);
	}

	//total size =	header size + num_frames * frame size + num_tags * tag size +
	//size of surfaces
	mem_size = sizeof(md3header_mem_t);
	mem_size += header->num_frames * sizeof(md3frame_mem_t);
	mem_size += header->num_tags * sizeof(md3tag_mem_t);
	mem_size += surf_size;

	Con_DPrintf("Loading md3 model...%s (%s)\n", header->filename, mod->name);

	// cannot allocate on hunk WTF !!!
	mem_head = (md3header_mem_t *)Cache_Alloc (&mod->cache, mem_size, mod->name);

	// ignore cache just check if the header contains data
	// dont try any funny moves here it wont work :(
	if (!mem_head) {
		return;	//cache alloc failed
	}

	//setup more mem stuff
	mod->type = mod_md3;
	mod->aliastype = MD3IDHEADER;
	mod->synctype = ST_RAND;

	//copy stuff across from disk buffer to memory
	posn = 0; //posn in new buffer

	//copy header
	Q_memcpy(mem_head, header, sizeof(md3header_t));
	posn += sizeof(md3header_mem_t);

	//posn of frames
	mem_head->offs_frames = posn;

	//copy frames
	Q_memcpy((byte *)mem_head + mem_head->offs_frames, (byte *)header + header->frame_offs, sizeof(md3frame_t)*header->num_frames);
	posn += sizeof(md3frame_mem_t)*header->num_frames;

	//posn of tags
	mem_head->offs_tags = posn;

	//copy tags
	Q_memcpy((byte *)mem_head + mem_head->offs_tags, (byte *)header + header->tag_offs, sizeof(md3tag_t)*header->num_tags);
	posn += sizeof(md3tag_mem_t)*header->num_tags;

	//posn of surfaces
	mem_head->offs_surfaces = posn;

	//get pointer to surface in file
	surf = (md3surface_t *)((byte *)header + header->surface_offs);

	//get pointer to surface in memory
	currentsurf = (md3surface_mem_t *)((byte *)mem_head + posn);
	surfstartposn = posn;

	for (i=0; i < header->num_surfaces; i++)
	{
		//copy size of surface
		Q_memcpy((byte *)mem_head + posn, (byte *)header + header->surface_offs, sizeof(md3surface_t));
		posn += sizeof(md3surface_mem_t);

		//posn of shaders for this surface
		currentsurf->offs_shaders = posn - surfstartposn;

		for (j=0; j < surf->num_surf_shaders; j++){
			//copy jth shader accross
			Q_memcpy((byte *)mem_head + posn, (byte *)surf + surf->shader_offs + j * sizeof(md3shader_t), sizeof(md3shader_t));
			posn += sizeof(md3shader_mem_t); //copyed non-mem into mem one
		}
		//posn of tris for this surface
		currentsurf->offs_tris = posn - surfstartposn;

		//copy tri
		Q_memcpy((byte *)mem_head + posn, (byte *)surf + surf->tris_offs, sizeof(md3tri_t) * surf->num_surf_tris);
		posn += sizeof(md3tri_mem_t) * surf->num_surf_tris;

		//posn of tex coords in this surface
		currentsurf->offs_tc = posn - surfstartposn;

		//copy st
		Q_memcpy((byte *)mem_head + posn, (byte *)surf + surf->tc_offs, sizeof(md3st_t) * surf->num_surf_verts);
		posn += sizeof(md3st_t) * surf->num_surf_verts;

		//posn points to surface->verts
		currentsurf->offs_verts = posn - surfstartposn;

		//next to have to be redone
		for (j=0; j < surf->num_surf_verts * surf->num_surf_frames; j++){
			float lat;
			float lng;

			//convert verts from shorts to floats
			md3vert_mem_t *mem_vert = (md3vert_mem_t *)((byte *)mem_head + posn);
			md3vert_t *disk_vert = (md3vert_t *)((byte *)surf + surf->vert_offs + j * sizeof(md3vert_t));

			mem_vert->vec[0] = (float)disk_vert->vec[0] * MD3_XYZ_SCALE;
			mem_vert->vec[1] = (float)disk_vert->vec[1] * MD3_XYZ_SCALE;
			mem_vert->vec[2] = (float)disk_vert->vec[2] * MD3_XYZ_SCALE;

			//work out normals
			lat = (disk_vert->normal + 255) * (2 * M_PI) / 255;
			lng = ((disk_vert->normal >> 8) & 255) * (2 * M_PI) / 255;

			mem_vert->normal[0] = cos (lat) * sin (lng);
			mem_vert->normal[1] = sin (lat) * sin (lng);
			mem_vert->normal[2] = cos (lng);

			posn += sizeof(md3vert_mem_t); //copyed non-mem into mem one
		}

		//point to next surf (or eof)
		surf = (md3surface_t*)((byte *)surf + surf->end_offs);

		//posn points to the end of this surface
		currentsurf->offs_end = posn;

		//next start of surf (if there is one)
		surfstartposn = posn;
	}

	//posn should now equal mem_size
	if (posn != mem_size){
		Con_Printf("Copied different ammount to what was worked out, copied: %i worked out: %i\n",posn, mem_size);
	}

	// load frames
	// Needed for lights "took me ages to complete this mess" :/
	// nasty hack but OMG ! it works :D Reckless:
	// from the looks off it this should also mean you can do Q1 style shadows 
	// from this struct. but its not working with glcmdlists "sigh"
	framein = (void *)((int) header + LittleLong(header->frame_offs));
	frameout = (void *)((int) mem_head + LittleLong(mem_head->offs_frames));
	for (i=0 ; i<mem_head->num_frames ; i++) {
		for (j=0 ; j<3 ; j++) {
			frameout->mins[j] = LittleFloat (framein->mins[j]);
			frameout->maxs[j] = LittleFloat (framein->maxs[j]);
			frameout->pos[j] = LittleFloat (framein->pos[j]);
		}
		frameout->radius = LittleFloat (framein->radius);
		mod->radius = max(mod->radius, frameout->radius);
		strcpy (frameout->name, framein->name);

		// whats the freakin size here ??? "i need an index pointer goddammit!"
		for (j=0 ; j<mem_head->num_surfaces-1 ; j++) {
			vec3_t bbox;

			VectorCopy (framein->verts[j].v, frameout->verts[j].v);
			frameout->verts[j].lightnormalindex = framein->verts[j].lightnormalindex;

			bbox[0] = frameout->verts[j].v[0] * frameout->mins[0] + frameout->maxs[0];
			bbox[1] = frameout->verts[j].v[1] * frameout->mins[1] + frameout->maxs[1];
			bbox[2] = frameout->verts[j].v[2] * frameout->mins[2] + frameout->maxs[2];

			// update bounding box
			if (bbox[0] < md3bboxmins[0]) md3bboxmins[0] = bbox[0];
			if (bbox[1] < md3bboxmins[1]) md3bboxmins[1] = bbox[1];
			if (bbox[2] < md3bboxmins[2]) md3bboxmins[2] = bbox[2];
			if (bbox[0] > md3bboxmaxs[0]) md3bboxmaxs[0] = bbox[0];
			if (bbox[1] > md3bboxmaxs[1]) md3bboxmaxs[1] = bbox[1];
			if (bbox[2] > md3bboxmaxs[2]) md3bboxmaxs[2] = bbox[2];
		}
		framein = (void *) &framein->verts[j].v[0];
		frameout = (void *) &frameout->verts[j].v[0];
	}
	VectorCopy (md3bboxmins, mod->mins);
	VectorCopy (md3bboxmaxs, mod->maxs);

	// TODO: Make a gl_command list for shadows :/

	// HACK: we dont have flags in md3 so give them some to enable effects ;)
	if (!strcmp (mod->name, "progs/g_shot.mdl") ||
		!strcmp (mod->name, "progs/g_nail.mdl") ||
		!strcmp (mod->name, "progs/g_nail2.mdl") ||
		!strcmp (mod->name, "progs/g_rock.mdl") ||
		!strcmp (mod->name, "progs/g_rock2.mdl") || 
		!strcmp (mod->name, "progs/g_light.mdl") ||
		!strcmp (mod->name, "progs/armor.mdl") ||
		!strcmp (mod->name, "progs/backpack.mdl") ||
		!strcmp (mod->name, "progs/w_g_key.mdl") ||
		!strcmp (mod->name, "progs/w_s_key.mdl") ||
		!strcmp (mod->name, "progs/m_g_key.mdl") ||
		!strcmp (mod->name, "progs/m_s_key.mdl") ||
		!strcmp (mod->name, "progs/b_g_key.mdl") ||
		!strcmp (mod->name, "progs/b_s_key.mdl") ||
		!strcmp (mod->name, "progs/quaddama.mdl") ||
		!strcmp (mod->name, "progs/invisibl.mdl") ||
		!strcmp (mod->name, "progs/invulner.mdl") ||
		!strcmp (mod->name, "progs/jetpack.mdl") || 
		!strcmp (mod->name, "progs/cube.mdl") ||
		!strcmp (mod->name, "progs/suit.mdl") ||
		!strcmp (mod->name, "progs/boots.mdl") ||
		!strcmp (mod->name, "progs/end1.mdl") ||
		!strcmp (mod->name, "progs/end2.mdl") ||
		!strcmp (mod->name, "progs/end3.mdl") ||
		!strcmp (mod->name, "progs/end4.mdl")) {
		mem_head->flags |= EF_ROTATE; 
	} else if (!strcmp (mod->name, "progs/missile.mdl")) {
		mem_head->flags |= EF_ROCKET;
	} else if (!strcmp (mod->name, "progs/gib1.mdl") || //EF_GIB
		!strcmp (mod->name, "progs/gib2.mdl") || 
		!strcmp (mod->name, "progs/gib3.mdl") || 
		!strcmp (mod->name, "progs/h_player.mdl") || 
		!strcmp (mod->name, "progs/h_dog.mdl") || 
		!strcmp (mod->name, "progs/h_mega.mdl") || 
		!strcmp (mod->name, "progs/h_guard.mdl") || 
		!strcmp (mod->name, "progs/h_wizard.mdl") || 
		!strcmp (mod->name, "progs/h_knight.mdl") || 
		!strcmp (mod->name, "progs/h_hellkn.mdl") || 
		!strcmp (mod->name, "progs/h_zombie.mdl") || 
		!strcmp (mod->name, "progs/h_shams.mdl") || 
		!strcmp (mod->name, "progs/h_shal.mdl") || 
		!strcmp (mod->name, "progs/h_ogre.mdl") ||
		!strcmp (mod->name, "progs/armor.mdl") ||
		!strcmp (mod->name, "progs/h_demon.mdl")) {
		mem_head->flags |= EF_GIB;
	} else if (!strcmp (mod->name, "progs/grenade.mdl")) {
		mem_head->flags |= EF_GRENADE;
	} else if (!strcmp (mod->name, "progs/w_spike.mdl")) {
		mem_head->flags |= EF_TRACER;
	} else if (!strcmp (mod->name, "progs/k_spike.mdl")) {
		mem_head->flags |= EF_TRACER2;
	} else if (!strcmp (mod->name, "progs/v_spike.mdl")) {
		mem_head->flags |= EF_TRACER3;
	} else if (!strcmp (mod->name, "progs/zom_gib.mdl")) {
		mem_head->flags |= EF_ZOMGIB;
	}
	mod->flags = LittleLong (mem_head->flags);
	mod->numframes = LittleLong (mem_head->num_frames);

	//get pointer to first surface
	currentsurf = (md3surface_mem_t *)((byte *)mem_head + mem_head->offs_surfaces);
	for (i=0; i<mem_head->num_surfaces; i++) {
		if (*(long *)currentsurf->id != MD3IDHEADER){
			Con_Printf("MD3 bad surface for: %s\n", mem_head->filename);
		} else {
			shader = (md3shader_mem_t *)((byte *)currentsurf + currentsurf->offs_shaders);
			for (j=0; j<currentsurf->num_surf_shaders; j++){
				sprintf (&name[0], "textures/md3skins/%s", shader[j].name);
				shader[j].texnum = GL_LoadTextureImage (&name[0], NULL, 0, 0, picmip_flag | TEX_BLEND);

				if (!shader[j].texnum) {
					sprintf (&name[0], "textures/md3boxes/%s", shader[j].name);
					shader[j].texnum = GL_LoadTextureImage (&name[0], NULL, 0, 0, picmip_flag | TEX_BLEND);
				}

				if (!shader[j].texnum) { 
					Con_Printf("Model: %s  Texture missing: %s\n", mod->name, shader[j].name);
				}
			}
		}
		currentsurf = (md3surface_mem_t *)((byte *)currentsurf + currentsurf->offs_end);
	}
}
