/*
 * Copyright (C) Volition, Inc. 1999.  All rights reserved.
 *
 * All source code herein is the property of Volition, Inc. You may not sell 
 * or otherwise commercially exploit the source or things you created based on the 
 * source.
 *
*/ 



#define MODEL_LIB

#include "model/model.h"
#include "model/modelsinc.h"
#include "graphics/2d.h"
#include "render/3dinternal.h"
#include "math/fvi.h"
#include "lighting/lighting.h"
#include "bmpman/bmpman.h"
#include "io/key.h"
#include "io/timer.h"
#include "mission/missionparse.h"
#include "nebula/neb.h"
#include "math/staticrand.h"
#include "particle/particle.h"
#include "ship/ship.h"
#include "cmdline/cmdline.h"
#include "gamesnd/gamesnd.h"
#include "globalincs/linklist.h"
#include "weapon/shockwave.h"
#include "parse/parselo.h"
#include "graphics/gropengllight.h"
#include "ship/shipfx.h"
#include "gamesequence/gamesequence.h"

#include <limits.h>


float model_radius = 0;

// Some debug variables used externally for displaying stats
#ifndef NDEBUG
int modelstats_num_polys = 0;
int modelstats_num_polys_drawn = 0;
int modelstats_num_verts = 0;
int modelstats_num_sortnorms = 0;
int modelstats_num_boxes = 0;
#endif

extern fix game_get_overall_frametime();	// for texture animation

typedef struct model_light {
	ubyte r, g, b;
	ubyte spec_r, spec_g, spec_b;
} model_light;

// a lighting object
typedef struct model_light_object {
	model_light *lights;

	int		objnum;
	int		skip;
	int		skip_max;
} model_light_object;

// -----------------------
// Local variables
//

static int Num_interp_verts_allocated = 0;
vec3d **Interp_verts = NULL;
static vertex *Interp_points = NULL;
static vertex *Interp_splode_points = NULL;
vec3d *Interp_splode_verts = NULL;
static int Interp_num_verts = 0;

static vertex **Interp_list = NULL;
static int  Num_interp_list_verts_allocated = 0;

static float Interp_box_scale = 1.0f; // this is used to scale both detail boxes and spheres
static vec3d Interp_render_box_min = ZERO_VECTOR;
static vec3d Interp_render_box_max = ZERO_VECTOR;
static float Interp_render_sphere_radius = 0.0f;
static vec3d Interp_render_sphere_offset = ZERO_VECTOR;

// -------------------------------------------------------------------
// lighting save stuff 
//

model_light_object Interp_lighting_temp;
model_light_object *Interp_lighting = &Interp_lighting_temp;
int Interp_use_saved_lighting = 0;
int Interp_saved_lighting_full = 0;
//
// lighting save stuff 
// -------------------------------------------------------------------


static int Num_interp_norms_allocated = 0;
static vec3d **Interp_norms = NULL;
static ubyte *Interp_light_applied = NULL;
static int Interp_num_norms = 0;
static ubyte *Interp_lights;

static float Interp_fog_level = 0.0f;

// Stuff to control rendering parameters
static color Interp_outline_color;
static int Interp_detail_level_locked = 0;
static uint Interp_flags = 0;
static uint Interp_tmap_flags = 0;
bool Interp_desaturate = false;

// If non-zero, then the subobject gets scaled by Interp_thrust_scale.
int Interp_thrust_scale_subobj = 0;
float Interp_thrust_scale = 0.1f;
static float Interp_thrust_scale_x = 0.0f;//added -bobboau
static float Interp_thrust_scale_y = 0.0f;//added -bobboau

static int Interp_thrust_bitmap = -1;
static int Interp_thrust_glow_bitmap = -1;
static float Interp_thrust_glow_noise = 1.0f;
static bool Interp_afterburner = false;

// Bobboau's thruster stuff
static int Interp_secondary_thrust_glow_bitmap = -1;
static int Interp_tertiary_thrust_glow_bitmap = -1;
static int Interp_distortion_thrust_bitmap = -1;
static float Interp_thrust_glow_rad_factor = 1.0f;
static float Interp_secondary_thrust_glow_rad_factor = 1.0f;
static float Interp_tertiary_thrust_glow_rad_factor = 1.0f;
static float Interp_distortion_thrust_rad_factor = 1.0f;
static float Interp_distortion_thrust_length_factor = 1.0f;
static float Interp_thrust_glow_len_factor = 1.0f;
static vec3d Interp_thrust_rotvel = ZERO_VECTOR;
static bool Interp_draw_distortion = true;

// Bobboau's warp stuff
static float Interp_warp_scale_x = 1.0f;
static float Interp_warp_scale_y = 1.0f;
static float Interp_warp_scale_z = 1.0f;
static int Interp_warp_bitmap = -1;
static float Interp_warp_alpha = -1.0f;

static int Interp_objnum = -1;

// if != -1, use this bitmap when rendering ship insignias
static int Interp_insignia_bitmap = -1;

// replacement - Goober5000
// updated - WMC
static int *Interp_new_replacement_textures = NULL;

static fix Interp_base_frametime = 0;

// if != -1, use this bitmap when rendering with a forced texture
static int Interp_forced_bitmap = -1;

// for rendering with the MR_ALL_XPARENT FLAG SET
static float Interp_xparent_alpha = 1.0f;

float Interp_light = 0.0f;

int Interp_multitex_cloakmap = -1;
int Interp_cloakmap_alpha = 255;

// our current level of detail (LOD)
int Interp_detail_level = 0;

static int FULLCLOAK = -1;

// current transformation texture
int Interp_no_flush = 0;

// forward references
int model_interp_sub(void *model_ptr, polymodel * pm, bsp_info *sm, int do_box_check);
void model_really_render(int model_num, matrix *orient, vec3d * pos, uint flags, int objnum = -1);
void model_interp_sortnorm_b2f(ubyte * p,polymodel * pm, bsp_info *sm, int do_box_check);
void model_interp_sortnorm_f2b(ubyte * p,polymodel * pm, bsp_info *sm, int do_box_check);
void (*model_interp_sortnorm)(ubyte * p,polymodel * pm, bsp_info *sm, int do_box_check) = model_interp_sortnorm_b2f;
int model_should_render_engine_glow(int objnum, int bank_obj);
void model_render_buffers(polymodel *pm, int mn, bool is_child = false);
void model_render_children_buffers(polymodel * pm, int mn, int detail_level);
int model_interp_get_texture(texture_info *tinfo, fix base_frametime);

//Valathil - Transparent object buffer
SCP_vector<transparent_submodel> transparent_submodels;

void model_deallocate_interp_data()
{
	if (Interp_verts != NULL)
		vm_free(Interp_verts);

	if (Interp_points != NULL)
		vm_free(Interp_points);

	if (Interp_splode_points != NULL)
		vm_free(Interp_splode_points);

	if (Interp_splode_verts != NULL)
		vm_free(Interp_splode_verts);

	if (Interp_norms != NULL)
		vm_free(Interp_norms);

	if (Interp_light_applied != NULL)
		vm_free(Interp_light_applied);

	if (Interp_lighting_temp.lights != NULL)
		vm_free(Interp_lighting_temp.lights);

	Num_interp_verts_allocated = 0;
	Num_interp_norms_allocated = 0;
}

extern void model_collide_allocate_point_list(int n_points);
extern void model_collide_free_point_list();

void model_allocate_interp_data(int n_verts = 0, int n_norms = 0, int n_list_verts = 0)
{
	static ubyte dealloc = 0;

	if (!dealloc) {
		atexit(model_deallocate_interp_data);
		atexit(model_collide_free_point_list);
		dealloc = 1;
	}

	Assert( (n_verts >= 0) && (n_norms >= 0) && (n_list_verts >= 0) );
	Assert( (n_verts || Num_interp_verts_allocated) && (n_norms || Num_interp_norms_allocated) /*&& (n_list_verts || Num_interp_list_verts_allocated)*/ );

	if (n_verts > Num_interp_verts_allocated) {
		if (Interp_verts != NULL) {
			vm_free(Interp_verts);
			Interp_verts = NULL;
		}
		// Interp_verts can't be reliably realloc'd so free and malloc it on each resize (no data needs to be carried over)
		Interp_verts = (vec3d**) vm_malloc( n_verts * sizeof(vec3d) );

		Interp_points = (vertex*) vm_realloc( Interp_points, n_verts * sizeof(vertex) );
		Interp_splode_points = (vertex*) vm_realloc( Interp_splode_points, n_verts * sizeof(vertex) );
		Interp_splode_verts = (vec3d*) vm_realloc( Interp_splode_verts, n_verts * sizeof(vec3d) );

		Num_interp_verts_allocated = n_verts;

		// model collide needs a similar size to resize it based on this new value
		model_collide_allocate_point_list( n_verts );
	}

	if (n_norms > Num_interp_norms_allocated) {
		if (Interp_norms != NULL) {
			vm_free(Interp_norms);
			Interp_norms = NULL;
		}
		// Interp_norms can't be reliably realloc'd so free and malloc it on each resize (no data needs to be carried over)
		Interp_norms = (vec3d**) vm_malloc( n_norms * sizeof(vec3d) );

		// these next two lighting things aren't values that need to be carried over, but we need to make sure they are 0 by default
		if (Interp_light_applied != NULL) {
			vm_free(Interp_light_applied);
			Interp_light_applied = NULL;
		}

		if (Interp_lighting_temp.lights != NULL) {
			vm_free(Interp_lighting_temp.lights);
			Interp_lighting_temp.lights = NULL;
		}

		Interp_light_applied = (ubyte*) vm_malloc( n_norms * sizeof(ubyte) );
		Interp_lighting_temp.lights = (model_light*) vm_malloc( n_norms * sizeof(model_light) );

		memset( Interp_light_applied, 0, n_norms * sizeof(ubyte) );
		memset( Interp_lighting_temp.lights, 0, n_norms * sizeof(model_light) );

		Num_interp_norms_allocated = n_norms;
	}

	// we should only get here if we are not in HTL mode
	if ( n_list_verts > Num_interp_list_verts_allocated ) {
		if (Interp_list != NULL) {
			vm_free(Interp_list);
			Interp_list = NULL;
		}

		Interp_list = (vertex**) vm_malloc( n_list_verts * sizeof(vertex) );
		Verify( Interp_list != NULL );

		Num_interp_list_verts_allocated = n_list_verts;
	}


	Interp_num_verts = n_verts;
	Interp_num_norms = n_norms;

	// check that everything is still usable (works in release and debug builds)
	Verify( Interp_points != NULL );
	Verify( Interp_splode_points != NULL );
	Verify( Interp_verts != NULL );
	Verify( Interp_splode_verts != NULL );
	Verify( Interp_norms != NULL );
	Verify( Interp_light_applied != NULL );
}

void model_setup_cloak(vec3d *shift, int full_cloak, int alpha)
{
	FULLCLOAK=full_cloak;
	int unit;
	if (full_cloak)
	{
		unit=0;
		Interp_multitex_cloakmap=0;
		model_set_insignia_bitmap(-1);
		model_set_forced_texture(CLOAKMAP);
		gr_set_cull(1);
		model_interp_sortnorm=model_interp_sortnorm_f2b;
	}
	else
	{
		unit=2;
		Interp_multitex_cloakmap=1;
	}

	Interp_cloakmap_alpha=alpha;
	gr_push_texture_matrix(unit);
	gr_translate_texture_matrix(unit,shift);
}

void model_finish_cloak(int full_cloak)
{
	int unit;
	if (full_cloak){		unit=0; gr_set_cull(0);}
	else				unit=2;

	gr_pop_texture_matrix(unit);
	Interp_multitex_cloakmap=-1;
	model_set_forced_texture(-1);
	FULLCLOAK=-1;
}

void interp_clear_instance()
{
	Interp_thrust_scale = 0.1f;
	Interp_thrust_scale_x = 0.0f;//added-Bobboau
	Interp_thrust_scale_y = 0.0f;//added-Bobboau
	Interp_thrust_bitmap = -1;
	Interp_thrust_glow_bitmap = -1;
	Interp_thrust_glow_noise = 1.0f;
	Interp_insignia_bitmap = -1;
	Interp_afterburner = false;

	// Bobboau's thruster stuff
	{
		Interp_thrust_glow_rad_factor = 1.0f;

		Interp_secondary_thrust_glow_bitmap = -1;
		Interp_secondary_thrust_glow_rad_factor = 1.0f;

		Interp_tertiary_thrust_glow_bitmap = -1;
		Interp_tertiary_thrust_glow_rad_factor = 1.0f;

		Interp_thrust_glow_len_factor = 1.0f;

		vm_vec_zero(&Interp_thrust_rotvel);
	}

	Interp_box_scale = 1.0f;
	Interp_render_box_min = vmd_zero_vector;
	Interp_render_box_max = vmd_zero_vector;
	Interp_render_sphere_radius = 0.0f;
	Interp_render_sphere_offset = vmd_zero_vector;
}

/**
 * Scales the engines thrusters by this much
 */
void model_set_thrust(int model_num, mst_info *mst)
{
	if (mst == NULL) {
		Int3();
		return;
	}

	Interp_thrust_scale = mst->length.xyz.z;
	Interp_thrust_scale_x = mst->length.xyz.x;
	Interp_thrust_scale_y = mst->length.xyz.y;

	CLAMP(Interp_thrust_scale, 0.1f, 1.0f);

	Interp_thrust_bitmap = mst->primary_bitmap;
	Interp_thrust_glow_bitmap = mst->primary_glow_bitmap;
	Interp_secondary_thrust_glow_bitmap = mst->secondary_glow_bitmap;
	Interp_tertiary_thrust_glow_bitmap = mst->tertiary_glow_bitmap;
	Interp_distortion_thrust_bitmap = mst->distortion_bitmap;

	Interp_thrust_glow_noise = mst->glow_noise;
	Interp_afterburner = mst->use_ab;

	if (mst->rotvel != NULL) {
		Interp_thrust_rotvel = *(mst->rotvel);
	} else {
		vm_vec_zero(&Interp_thrust_rotvel);
	}

	Interp_thrust_glow_rad_factor = mst->glow_rad_factor;
	Interp_secondary_thrust_glow_rad_factor = mst->secondary_glow_rad_factor;
	Interp_tertiary_thrust_glow_rad_factor = mst->tertiary_glow_rad_factor;
	Interp_thrust_glow_len_factor = mst->glow_length_factor;
	Interp_distortion_thrust_rad_factor = mst->distortion_rad_factor;
	Interp_distortion_thrust_length_factor = mst->distortion_length_factor;

	Interp_draw_distortion = mst->draw_distortion;
}

bool splodeing = false;
int splodeingtexture = -1;
float splode_level = 0.0f;

float GEOMETRY_NOISE = 0.0f;

// Point list
// +0      int         id
// +4      int         size
// +8      int         n_verts
// +12     int         n_norms
// +16     int         offset from start of chunk to vertex data
// +20     n_verts*char    norm_counts
// +offset             vertex data. Each vertex n is a point followed by norm_counts[n] normals.
void model_interp_splode_defpoints(ubyte * p, polymodel *pm, bsp_info *sm, float dist)
{
	if(dist==0.0f)return;

	if(dist<0.0f)dist*=-1.0f;

	int n;
	int nverts = w(p+8);	
	int offset = w(p+16);
	int nnorms = 0;

	ubyte * normcount = p+20;
	vertex *dest = Interp_splode_points;
	vec3d *src = vp(p+offset);

	for (n = 0; n < nverts; n++) {
		nnorms += normcount[n];
	}

	model_allocate_interp_data(nverts, nnorms);

	vec3d dir;

	for (n=0; n<nverts; n++ )	{	
		Interp_splode_verts[n] = *src;		
			
		src++;

		vm_vec_avg_n(&dir, normcount[n], src);
		vm_vec_normalize(&dir);

		for(int i=0; i<normcount[n]; i++)src++;

		vm_vec_scale_add2(&Interp_splode_verts[n], &dir, dist);

		g3_rotate_vertex(dest, &Interp_splode_verts[n]);
		
		dest++;

	}

}

// Point list
// +0      int         id
// +4      int         size
// +8      int         n_verts
// +12     int         n_norms
// +16     int         offset from start of chunk to vertex data
// +20     n_verts*char    norm_counts
// +offset             vertex data. Each vertex n is a point followed by norm_counts[n] normals.
void model_interp_defpoints(ubyte * p, polymodel *pm, bsp_info *sm)
{
	if(splodeing)model_interp_splode_defpoints(p, pm, sm, splode_level*model_radius);

	int i, n;
	int nverts = w(p+8);	
	int offset = w(p+16);
	int next_norm = 0;
	int nnorms = 0;

	ubyte * normcount = p+20;
	vertex *dest = NULL;
	vec3d *src = vp(p+offset);

	// Get pointer to lights
	Interp_lights = p+20+nverts;

	for (i = 0; i < nverts; i++) {
		nnorms += normcount[i];
	}

	// allocate new Interp data if size is greater than we already have ready to use
	model_allocate_interp_data(nverts, nnorms);

	dest = Interp_points;

	Assert( dest != NULL );

	#ifndef NDEBUG
	modelstats_num_verts += nverts;
	#endif


	if (Interp_thrust_scale_subobj)	{

		// Only scale vertices that aren't on the "base" of 
		// the effect.  Base is something Adam decided to be
		// anything under 1.5 meters, hence the 1.5f.
		float min_thruster_dist = -1.5f;

		if ( Interp_flags & MR_IS_MISSILE )	{
			min_thruster_dist = 0.5f;
		}

		for (n=0; n<nverts; n++ )	{
			vec3d tmp;

			Interp_verts[n] = src;

			// Only scale vertices that aren't on the "base" of 
			// the effect.  Base is something Adam decided to be
			// anything under 1.5 meters, hence the 1.5f.
			if ( src->xyz.z < min_thruster_dist )	{
				tmp.xyz.x = src->xyz.x * 1.0f;
				tmp.xyz.y = src->xyz.y * 1.0f;
				tmp.xyz.z = src->xyz.z * Interp_thrust_scale;
			} else {
				tmp = *src;
			}
			
			g3_rotate_vertex(dest,&tmp);
		
			src++;		// move to normal

			for (i=0; i<normcount[n]; i++ )	{
				Interp_light_applied[next_norm] = 0;
				Interp_norms[next_norm] = src;

				next_norm++;
				src++;
			}
			dest++;
		} 
	} else if ( (Interp_warp_scale_x != 1.0f) || (Interp_warp_scale_y != 1.0f) || (Interp_warp_scale_z != 1.0f)) {
		for (n=0; n<nverts; n++ )	{
			vec3d tmp;

			Interp_verts[n] = src;

			tmp.xyz.x = (src->xyz.x) * Interp_warp_scale_x;
			tmp.xyz.y = (src->xyz.y) * Interp_warp_scale_y;
			tmp.xyz.z = (src->xyz.z) * Interp_warp_scale_z;
			
			g3_rotate_vertex(dest,&tmp);
		
			src++;		// move to normal

			for (i=0; i<normcount[n]; i++ )	{
				Interp_light_applied[next_norm] = 0;
				Interp_norms[next_norm] = src;

				next_norm++;
				src++;
			}
			dest++;
		} 
	} else {
		vec3d point;

		for (n=0; n<nverts; n++ )	{	

			if(!Cmdline_nohtl) {
				if(GEOMETRY_NOISE!=0.0f){
					GEOMETRY_NOISE = model_radius / 50;

					Interp_verts[n] = src;	
					point.xyz.x = src->xyz.x + frand_range(GEOMETRY_NOISE,-GEOMETRY_NOISE);
					point.xyz.y = src->xyz.y + frand_range(GEOMETRY_NOISE,-GEOMETRY_NOISE);
					point.xyz.z = src->xyz.z + frand_range(GEOMETRY_NOISE,-GEOMETRY_NOISE);
							
					g3_rotate_vertex(dest, &point);
				}else{
					Interp_verts[n] = src;	
					g3_rotate_vertex(dest, src);
				}
			}
			else {
				Interp_verts[n] = src; 	 	 

				g3_rotate_vertex(dest, src);
			}

			src++;		// move to normal

			for (i=0; i<normcount[n]; i++ )	{
				Interp_light_applied[next_norm] = 0;
				Interp_norms[next_norm] = src;

				next_norm++;
				src++;
			}
			dest++;
		}
	}

	Interp_num_norms = next_norm;
}

matrix *Interp_orient;
vec3d *Interp_pos;


// Flat Poly
// +0      int         id
// +4      int         size 
// +8      vec3d      normal
// +20     vec3d      center
// +32     float       radius
// +36     int         nverts
// +40     byte        red
// +41     byte        green
// +42     byte        blue
// +43     byte        pad
// +44     nverts*short*short  vertlist, smoothlist
void model_interp_flatpoly(ubyte * p,polymodel * pm)
{
	int nv = w(p+36);

	if ( nv < 0 )
		return;

	#ifndef NDEBUG
	modelstats_num_polys++;
	#endif

	if ( !g3_check_normal_facing(vp(p+20), vp(p+8)) )
		return;
	

	int i;
	short * verts = (short *)(p+44);

	int max_n_verts = 0;
	int max_n_norms = 0;

	// slow?  yes.  safe?  yes.
	for (i = 0; i < nv; i++) {
		max_n_verts = MAX(verts[i*2+0] + 1, max_n_verts);
		max_n_norms = MAX(verts[i*2+1] + 1, max_n_norms);
	}

	model_allocate_interp_data(max_n_verts, max_n_norms, nv);

	for (i = 0; i < nv; i++) {
		Interp_list[i] = &Interp_points[verts[i*2]];

		if ( Interp_flags & MR_NO_LIGHTING )	{
				Interp_list[i]->r = 191;
				Interp_list[i]->g = 191;
				Interp_list[i]->b = 191;
		} else {
			int vertnum = verts[i*2+0];
			int norm = verts[i*2+1];
	
			if ( Interp_flags & MR_NO_SMOOTHING )	{
				light_apply_rgb( &Interp_list[i]->r, &Interp_list[i]->g, &Interp_list[i]->b, Interp_verts[vertnum], vp(p+8), Interp_light );
			} else {
				// if we're not using saved lighting
				if ( !Interp_use_saved_lighting && !Interp_light_applied[norm] )	{
					light_apply_rgb( &Interp_lighting->lights[norm].r, &Interp_lighting->lights[norm].g, &Interp_lighting->lights[norm].b, Interp_verts[vertnum], vp(p+8), Interp_light );
					Interp_light_applied[norm] = 1;
				}

				Interp_list[i]->r = Interp_lighting->lights[norm].r;
				Interp_list[i]->g = Interp_lighting->lights[norm].g;
				Interp_list[i]->b = Interp_lighting->lights[norm].b;
			}
		}
	}

	// HACK!!! FIX ME!!! I'M SLOW!!!!
	if ( !(Interp_flags & MR_SHOW_OUTLINE_PRESET) )	{
		gr_set_color( *(p+40), *(p+41), *(p+42) );
	}

	if ( !(Interp_flags & MR_NO_POLYS))	{
		g3_draw_poly( nv, Interp_list, TMAP_FLAG_GOURAUD | TMAP_FLAG_RGB );	
	}

	if (Interp_flags & (MR_SHOW_OUTLINE|MR_SHOW_OUTLINE_PRESET))	{
		int j;

		if ( Interp_flags & MR_SHOW_OUTLINE )	{
			gr_set_color_fast( &Interp_outline_color );
		}

		for (i = 0; i < nv; i++) {
			j = (i + 1) % nv;
			g3_draw_line(Interp_list[i], Interp_list[j]);
		}
	}
}

void model_interp_edge_alpha( ubyte *param_r, ubyte *param_g, ubyte *param_b, vec3d *pnt, vec3d *norm, float alpha, bool invert = false)
{
	vec3d r;

	vm_vec_sub(&r, &View_position, pnt);
	vm_vec_normalize(&r);

	float d = vm_vec_dot(&r, norm);

	if (d < 0.0f)
		d = -d;

	if (invert)
		*param_r = *param_g = *param_b = ubyte( fl2i((1.0f - d) * 254.0f * alpha));
	else
		*param_r = *param_g = *param_b = ubyte( fl2i(d * 254.0f * alpha) );
}

// Textured Poly
// +0      int         id
// +4      int         size 
// +8      vec3d      normal
// +20     vec3d      center
// +32     float       radius
// +36     int         nverts
// +40     int         tmap_num
// +44     nverts*(model_tmap_vert) vertlist (n,u,v)
extern int Tmap_show_layers;

int Interp_subspace = 0;
float Interp_subspace_offset_u = 0.0f;
float Interp_subspace_offset_v = 0.0f;
ubyte Interp_subspace_r = 255;
ubyte Interp_subspace_g = 255;
ubyte Interp_subspace_b = 255;

void model_interp_tmappoly(ubyte * p,polymodel * pm)
{
	int i;
	int nv;
	model_tmap_vert *verts;
	int cull = 0;

	// Goober5000
	int tmap_num = w(p+40);
	texture_map *tmap = &pm->maps[tmap_num];
	texture_info *tbase = &tmap->textures[TM_BASE_TYPE];
	texture_info *tglow = &tmap->textures[TM_GLOW_TYPE];
	int rt_begin_index = tmap_num*TM_NUM_TYPES;

	// Goober5000
	Interp_base_frametime = 0;
	if (Interp_objnum >= 0)
	{
		object *objp = &Objects[Interp_objnum];
		if (objp->type == OBJ_SHIP)
			Interp_base_frametime = Ships[objp->instance].base_texture_anim_frametime;
	}

	int is_invisible = 0;

	if (Interp_warp_bitmap < 0) {
		if ( (!Interp_thrust_scale_subobj) && (tbase->GetTexture() < 0) ) {
			// Don't draw invisible polygons.
			if ( !(Interp_flags & MR_SHOW_INVISIBLE_FACES) )
				return;
			else
				is_invisible = 1;
		}
	}

	nv = w(p+36);

	#ifndef NDEBUG
	modelstats_num_polys++;
	#endif

	if ( nv < 0 )
		return;

	verts = (model_tmap_vert *)(p+44);

	int max_n_verts = 0;
	int max_n_norms = 0;

	for (i = 0; i < nv; i++) {
		max_n_verts = MAX(verts[i].vertnum + 1, max_n_verts);
		max_n_norms = MAX(verts[i].normnum + 1, max_n_norms);
	}

	model_allocate_interp_data(max_n_verts, max_n_norms, nv);

	if ( !Cmdline_nohtl ) {
		if (Interp_warp_bitmap < 0) {
			if (!g3_check_normal_facing(vp(p+20),vp(p+8)) && !(Interp_flags & MR_NO_CULL)){
				if(!splodeing) return;
			}
		}

		if(splodeing){
			float salpha = 1.0f - splode_level;
			for (i=0;i<nv;i++){
				Interp_list[i] = &Interp_splode_points[verts[i].vertnum];
				Interp_list[i]->texture_position.u = verts[i].u*2;
				Interp_list[i]->texture_position.v = verts[i].v*2;
				Interp_list[i]->r = (unsigned char)(255*salpha);
				Interp_list[i]->g = (unsigned char)(250*salpha);
				Interp_list[i]->b = (unsigned char)(200*salpha);
				model_interp_edge_alpha(&Interp_list[i]->r, &Interp_list[i]->g, &Interp_list[i]->b, Interp_verts[verts[i].vertnum], Interp_norms[verts[i].normnum], salpha, false);
			}
			cull = gr_set_cull(0);
			gr_set_bitmap( splodeingtexture, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, salpha );
			g3_draw_poly( nv, Interp_list,  TMAP_FLAG_TEXTURED|TMAP_FLAG_GOURAUD);
			gr_set_cull(cull);
			return;
		}
	}

	for (i=0;i<nv;i++)	{
		Interp_list[i] = &Interp_points[verts[i].vertnum];

		Interp_list[i]->texture_position.u = verts[i].u;
		Interp_list[i]->texture_position.v = verts[i].v;
		
		if ( Interp_subspace )	{
			Interp_list[i]->texture_position.v += Interp_subspace_offset_u;
			Interp_list[i]->texture_position.u += Interp_subspace_offset_v;
			Interp_list[i]->r = Interp_subspace_r;
			Interp_list[i]->g = Interp_subspace_g;
			Interp_list[i]->b = Interp_subspace_b;
		} else {
			if ( (Interp_flags & MR_NO_LIGHTING) || (tmap->is_ambient))	{	//gets the ambient glow to work
				Interp_list[i]->r = 191;
				Interp_list[i]->g = 191;
				Interp_list[i]->b = 191;
				
				Interp_list[i]->spec_r = 0;
				Interp_list[i]->spec_g = 0;
				Interp_list[i]->spec_b = 0;
				
				if (Interp_flags & MR_EDGE_ALPHA) {
					model_interp_edge_alpha(&Interp_list[i]->r, &Interp_list[i]->g, &Interp_list[i]->b, Interp_verts[verts[i].vertnum], Interp_norms[verts[i].normnum], Interp_warp_alpha, false);
				}

				if (Interp_flags & MR_CENTER_ALPHA) {
					model_interp_edge_alpha(&Interp_list[i]->r, &Interp_list[i]->g, &Interp_list[i]->b, Interp_verts[verts[i].vertnum], Interp_norms[verts[i].normnum], Interp_warp_alpha, true);
				}

				SPECMAP = -1;
				NORMMAP = -1;
				HEIGHTMAP = -1;
				MISCMAP = -1;
			} else {

				int vertnum = verts[i].vertnum;
				int norm = verts[i].normnum;
		
				if ( Interp_flags & MR_NO_SMOOTHING )	{
					light_apply_rgb( &Interp_list[i]->r, &Interp_list[i]->g, &Interp_list[i]->b, Interp_verts[vertnum], vp(p+8), Interp_light );
					if((Detail.lighting > 2) && (Interp_detail_level < 2) && Cmdline_spec )
						light_apply_specular( &Interp_list[i]->spec_r, &Interp_list[i]->spec_g, &Interp_list[i]->spec_b, Interp_verts[vertnum], vp(p+8),  &View_position);
				} else {					
					// if we're applying lighting as normal, and not using saved lighting
					if ( !Interp_use_saved_lighting && !Interp_light_applied[norm] )	{

						light_apply_rgb( &Interp_lighting->lights[norm].r, &Interp_lighting->lights[norm].g, &Interp_lighting->lights[norm].b, Interp_verts[vertnum], Interp_norms[norm], Interp_light );
						if((Detail.lighting > 2) && (Interp_detail_level < 2) && Cmdline_spec )
							light_apply_specular( &Interp_lighting->lights[norm].spec_r, &Interp_lighting->lights[norm].spec_g, &Interp_lighting->lights[norm].spec_b, Interp_verts[vertnum], Interp_norms[norm],  &View_position);

						Interp_light_applied[norm] = 1;
					}

					Interp_list[i]->spec_r = Interp_lighting->lights[norm].spec_r;
					Interp_list[i]->spec_g = Interp_lighting->lights[norm].spec_g;
					Interp_list[i]->spec_b = Interp_lighting->lights[norm].spec_b;

					Interp_list[i]->r = Interp_lighting->lights[norm].r;
					Interp_list[i]->g = Interp_lighting->lights[norm].g;
					Interp_list[i]->b = Interp_lighting->lights[norm].b;
				}
			}
		}
	}

	#ifndef NDEBUG
	modelstats_num_polys_drawn++;
	#endif

	if (!(Interp_flags & MR_NO_POLYS) )		{
		if ( is_invisible )	{
			gr_set_color( 0, 255, 0 );
			g3_draw_poly( nv, Interp_list, 0 );		
		} else if (Interp_thrust_scale_subobj)	{
			if ( (Interp_thrust_bitmap >= 0) && (Interp_thrust_scale > 0.0f) ) {
				gr_set_bitmap( Interp_thrust_bitmap, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, 1.2f );
				g3_draw_poly( nv, Interp_list, TMAP_FLAG_TEXTURED );		
			} else {
				if ( !(Interp_flags & MR_SHOW_OUTLINE_PRESET) )	{
					gr_set_color( 128, 128, 255 );
				}
				uint tflags = Interp_tmap_flags;
				tflags &=  (~(TMAP_FLAG_TEXTURED|TMAP_FLAG_TILED|TMAP_FLAG_CORRECT));
				g3_draw_poly( nv, Interp_list, tflags );		
			}
		} else if (Interp_warp_bitmap >= 0) {	//warpin effect-Bobboau
			gr_set_bitmap( Interp_warp_bitmap, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, Interp_warp_alpha );
			g3_draw_poly( nv, Interp_list, TMAP_FLAG_TEXTURED );
		}else{
			// all textured polys go through here
			if ( Interp_tmap_flags & TMAP_FLAG_TEXTURED )	{
				// subspace special case
				if (Interp_subspace) {										
					gr_set_bitmap( tbase->GetTexture(), GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, 1.2f );					
				}
				// all other textures
				else {					
					int texture;

					// if we're rendering a nebula background pof, maybe select a custom texture
					if((Interp_flags & MR_FORCE_TEXTURE) && (Interp_forced_bitmap >= 0)){
						texture = Interp_forced_bitmap;
					}else if((Interp_new_replacement_textures != NULL) && (Interp_new_replacement_textures[rt_begin_index + TM_BASE_TYPE] >= 0)){
						texture_info tt = texture_info(Interp_new_replacement_textures[rt_begin_index + TM_BASE_TYPE]);
						texture = model_interp_get_texture(&tt, Interp_base_frametime);
					} else {
						// pick the texture, animating it if necessary
						texture = model_interp_get_texture(tbase, Interp_base_frametime);
						
						// doing glow maps?
						if ( !(Interp_flags & MR_NO_GLOWMAPS) && (tglow->GetTexture() >= 0) ) {
							// shockwaves are special, their current frame has to come out of the shockwave code to get the timing correct
							if ( (Interp_objnum >= 0) && (Objects[Interp_objnum].type == OBJ_SHOCKWAVE) && (tglow->GetNumFrames() > 1) ) {
								GLOWMAP = tglow->GetTexture() + shockwave_get_framenum(Objects[Interp_objnum].instance, tglow->GetNumFrames());
							} else {
								GLOWMAP = model_interp_get_texture(tglow, Interp_base_frametime);
							}
						}

						if((Detail.lighting > 2)  && (Interp_detail_level < 2))
						{
							// likewise, etc.
							SPECMAP = model_interp_get_texture(&tmap->textures[TM_SPECULAR_TYPE], Interp_base_frametime);
							NORMMAP = model_interp_get_texture(&tmap->textures[TM_NORMAL_TYPE], Interp_base_frametime);
							HEIGHTMAP = model_interp_get_texture(&tmap->textures[TM_HEIGHT_TYPE], Interp_base_frametime);
						}

						MISCMAP = model_interp_get_texture(&tmap->textures[TM_MISC_TYPE], Interp_base_frametime);
					}

					//*****
					//WMC - now do other replacements
					if(Interp_new_replacement_textures != NULL)
					{
						texture_info tinfo;
						for(int tmn = TM_BASE_TYPE+1; tmn < TM_NUM_TYPES; tmn++)
						{
							int tex = Interp_new_replacement_textures[rt_begin_index + tmn];
							if(tex < 0)
								continue;

							tinfo = texture_info(tex);

							//Figure out actual texture to use
							tex = model_interp_get_texture(&tinfo, Interp_base_frametime);
							switch(tmn)
							{
								case TM_GLOW_TYPE:
									GLOWMAP = tex;
									break;
								case TM_SPECULAR_TYPE:
									SPECMAP = tex;
									break;
								case TM_NORMAL_TYPE:
									NORMMAP = tex;
									break;
								case TM_HEIGHT_TYPE:
									HEIGHTMAP = tex;
									break;
								case TM_MISC_TYPE:
									MISCMAP = tex;
								default:
									break;
							}
						}
					}

					// muzzle flashes draw xparent
					if(Interp_flags & MR_ALL_XPARENT){
						gr_set_bitmap( texture, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, Interp_xparent_alpha );
					} else {
						if(tmap->is_transparent) {	//trying to get transperent textures-Bobboau
							gr_set_bitmap( texture, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, 0.8f );
						}else{
							gr_set_bitmap( texture );
						}
					}
				}
			} else {
				if ( !(Interp_flags & MR_SHOW_OUTLINE_PRESET) )	{
					gr_set_color( 128, 128, 128 );
				}
			}

			if ( Interp_subspace )	{
				g3_draw_poly( nv, Interp_list,  TMAP_FLAG_TEXTURED|TMAP_FLAG_TILED|TMAP_FLAG_CORRECT );		
			} else {				
				if(Interp_flags & MR_ALL_XPARENT){
					g3_draw_poly( nv, Interp_list, Interp_tmap_flags );
				} else {
					g3_draw_poly( nv, Interp_list, Interp_tmap_flags|TMAP_FLAG_NONDARKENING );		
				}
			}
		}
	}

	GLOWMAP = -1;
	SPECMAP = -1;
	NORMMAP = -1;
	HEIGHTMAP = -1;
	MISCMAP = -1;

	if (Interp_flags & (MR_SHOW_OUTLINE|MR_SHOW_OUTLINE_PRESET) )	{
	
		if ( Interp_flags & MR_SHOW_OUTLINE )	{
			gr_set_color_fast( &Interp_outline_color );
		}

		for (i=0; i<nv; i++ )	{
			int j = (i + 1) % nv;
	   		g3_draw_line(Interp_list[i], Interp_list[j]);
		}
	}

}

// Sortnorms
// +0      int         id
// +4      int         size 
// +8      vec3d      normal
// +20     vec3d      normal_point
// +32     int         tmp=0
// 36     int     front offset
// 40     int     back offset
// 44     int     prelist offset
// 48     int     postlist offset
// 52     int     online offset
void model_interp_sortnorm_b2f(ubyte * p,polymodel * pm, bsp_info *sm, int do_box_check)
{
	#ifndef NDEBUG
	modelstats_num_sortnorms++;
	#endif

	int frontlist = w(p+36);
	int backlist = w(p+40);
	int prelist = w(p+44);
	int postlist = w(p+48);
	int onlist = w(p+52);

	if (prelist) model_interp_sub(p+prelist,pm,sm,do_box_check);		// prelist

	if (g3_check_normal_facing(vp(p+20),vp(p+8))) {		//facing

		//draw back then front

		if (backlist) model_interp_sub(p+backlist,pm,sm,do_box_check);

		if (onlist) model_interp_sub(p+onlist,pm,sm,do_box_check);			//onlist

		if (frontlist) model_interp_sub(p+frontlist,pm,sm,do_box_check);

	}	else {			//not facing.  draw front then back

		if (frontlist) model_interp_sub(p+frontlist,pm,sm,do_box_check);

		if (onlist) model_interp_sub(p+onlist,pm,sm,do_box_check);			//onlist

		if (backlist) model_interp_sub(p+backlist,pm,sm,do_box_check);
	}

	if (postlist) model_interp_sub(p+postlist,pm,sm,do_box_check);		// postlist

}

// Sortnorms
// +0      int         id
// +4      int         size 
// +8      vec3d      normal
// +20     vec3d      normal_point
// +32     int         tmp=0
// 36     int     front offset
// 40     int     back offset
// 44     int     prelist offset
// 48     int     postlist offset
// 52     int     online offset
void model_interp_sortnorm_f2b(ubyte * p,polymodel * pm, bsp_info *sm, int do_box_check)
{
	#ifndef NDEBUG
	modelstats_num_sortnorms++;
	#endif

	int frontlist = w(p+36);
	int backlist = w(p+40);
	int prelist = w(p+44);
	int postlist = w(p+48);
	int onlist = w(p+52);

	if (postlist) model_interp_sub(p+postlist,pm,sm,do_box_check);		// postlist

	if (g3_check_normal_facing(vp(p+20),vp(p+8))) {		//facing

		if (frontlist) model_interp_sub(p+frontlist,pm,sm,do_box_check);

		if (onlist) model_interp_sub(p+onlist,pm,sm,do_box_check);			//onlist

		if (backlist) model_interp_sub(p+backlist,pm,sm,do_box_check);

	}	else {			//not facing.  draw front then back

		//draw back then front

		if (backlist) model_interp_sub(p+backlist,pm,sm,do_box_check);

		if (onlist) model_interp_sub(p+onlist,pm,sm,do_box_check);			//onlist

		if (frontlist) model_interp_sub(p+frontlist,pm,sm,do_box_check);

	}

	if (prelist) model_interp_sub(p+prelist,pm,sm,do_box_check);		// prelist
}

void model_draw_debug_points( polymodel *pm, bsp_info * submodel )
{
	if ( Interp_flags & MR_SHOW_OUTLINE_PRESET )	{
		return;
	}

	// Draw a red pivot point
	gr_set_color(128,0,0);
	g3_draw_sphere_ez(&vmd_zero_vector, 2.0f );

	// Draw a green center of mass when drawing the hull
	if ( submodel && (submodel->parent==-1) )	{
		gr_set_color(0,128,0);
		g3_draw_sphere_ez( &pm->center_of_mass, 1.0f );
	}

	if ( submodel )	{
		// Draw a blue center point
		gr_set_color(0,0,128);
		g3_draw_sphere_ez( &submodel->geometric_center, 0.9f );
	}
	
	// Draw the bounding box
	int i;
	vertex pts[8];

	if ( submodel )	{
		for (i=0; i<8; i++ )	{
			g3_rotate_vertex( &pts[i], &submodel->bounding_box[i] );
		}
		gr_set_color(128,128,128);
		g3_draw_line( &pts[0], &pts[1] );
		g3_draw_line( &pts[1], &pts[2] );
		g3_draw_line( &pts[2], &pts[3] );
		g3_draw_line( &pts[3], &pts[0] );

		g3_draw_line( &pts[4], &pts[5] );
		g3_draw_line( &pts[5], &pts[6] );
		g3_draw_line( &pts[6], &pts[7] );
		g3_draw_line( &pts[7], &pts[4] );

		g3_draw_line( &pts[0], &pts[4] );
		g3_draw_line( &pts[1], &pts[5] );
		g3_draw_line( &pts[2], &pts[6] );
		g3_draw_line( &pts[3], &pts[7] );
	} else {
		gr_set_color(0,255,0);

		int j;
		for (j=0; j<8; j++ )	{

			vec3d	bounding_box[8];		// caclulated fron min/max
			model_calc_bound_box(bounding_box,&pm->octants[j].min,&pm->octants[j].max);

			for (i=0; i<8; i++ )	{
				g3_rotate_vertex( &pts[i], &bounding_box[i] );
			}
			gr_set_color(128,0,0);
			g3_draw_line( &pts[0], &pts[1] );
			g3_draw_line( &pts[1], &pts[2] );
			g3_draw_line( &pts[2], &pts[3] );
			g3_draw_line( &pts[3], &pts[0] );

			g3_draw_line( &pts[4], &pts[5] );
			g3_draw_line( &pts[5], &pts[6] );
			g3_draw_line( &pts[6], &pts[7] );
			g3_draw_line( &pts[7], &pts[4] );

			g3_draw_line( &pts[0], &pts[4] );
			g3_draw_line( &pts[1], &pts[5] );
			g3_draw_line( &pts[2], &pts[6] );
			g3_draw_line( &pts[3], &pts[7] );			
		}		
	}
}

/**
 * Debug code to show all the paths of a model
 */
void model_draw_paths( int model_num )
{
	int i,j;
	vec3d pnt;
	polymodel * pm;	

	if ( Interp_flags & MR_SHOW_OUTLINE_PRESET )	{
		return;
	}

	pm = model_get(model_num);

	if (pm->n_paths<1){
		return;
	}	

	for (i=0; i<pm->n_paths; i++ )	{
		vertex prev_pnt;

		for (j=0; j<pm->paths[i].nverts; j++ )	{
			// Rotate point into world coordinates			
			pnt = pm->paths[i].verts[j].pos;

			// Pnt is now the x,y,z world coordinates of this vert.
			// For this example, I am just drawing a sphere at that
			// point.
			{
				vertex tmp;
				g3_rotate_vertex(&tmp,&pnt);

				if ( pm->paths[i].verts[j].nturrets > 0 ){
					gr_set_color( 0, 0, 255 );						// draw points covered by turrets in blue
				} else {
					gr_set_color( 255, 0, 0 );
				}

				g3_draw_sphere( &tmp, 0.5f );

				if (j){
					g3_draw_line(&prev_pnt, &tmp);
				}

				prev_pnt = tmp;
			}
		}
	}
}

/**
 * Debug code to show all the paths of a model
 */
void model_draw_paths_htl( int model_num )
{
	int i,j;
	vec3d pnt;
	vec3d prev_pnt;
	polymodel * pm;	

	if ( Interp_flags & MR_SHOW_OUTLINE_PRESET )	{
		return;
	}

	pm = model_get(model_num);

	if (pm->n_paths<1){
		return;
	}	

	int cull = gr_set_cull(0);
	for (i=0; i<pm->n_paths; i++ )	{
		for (j=0; j<pm->paths[i].nverts; j++ )
		{
			// Rotate point into world coordinates			
			pnt = pm->paths[i].verts[j].pos;

			// Pnt is now the x,y,z world coordinates of this vert.
			// For this example, I am just drawing a sphere at that
			// point.

			vertex tmp;
			g3_rotate_vertex(&tmp,&pnt);

			if ( pm->paths[i].verts[j].nturrets > 0 ){
				gr_set_color( 0, 0, 255 );						// draw points covered by turrets in blue
			} else {
				gr_set_color( 255, 0, 0 );
			}

			g3_draw_htl_sphere(&pnt, 0.5f);
			
			if (j)
			{
				g3_draw_htl_line(&prev_pnt, &pnt);
			}

			prev_pnt = pnt;
		}
	}

	gr_set_cull(cull);
}

/**
 * Docking bay and fighter bay paths
 */
void model_draw_bay_paths_htl(int model_num)
{
	int idx, s_idx;
	vec3d v1, v2;

	polymodel *pm = model_get(model_num);
	if(pm == NULL){
		return;
	}

	int cull = gr_set_cull(0);
	// render docking bay normals
	gr_set_color(0, 255, 0);
	for(idx=0; idx<pm->n_docks; idx++){
		for(s_idx=0; s_idx<pm->docking_bays[idx].num_slots; s_idx++){
			v1 = pm->docking_bays[idx].pnt[s_idx];
			vm_vec_scale_add(&v2, &v1, &pm->docking_bays[idx].norm[s_idx], 10.0f);

			// draw the point and normal
			g3_draw_htl_sphere(&v1, 2.0);
			g3_draw_htl_line(&v1, &v2);
		}
	}

	// render figher bay paths
	gr_set_color(0, 255, 255);
		
	// iterate through the paths that exist in the polymodel, searching for $bayN pathnames
	for (idx = 0; idx<pm->n_paths; idx++) {
		if ( !strnicmp(pm->paths[idx].name, NOX("$bay"), 4) ) {						
			for(s_idx=0; s_idx<pm->paths[idx].nverts-1; s_idx++){
				v1 = pm->paths[idx].verts[s_idx].pos;
				v2 = pm->paths[idx].verts[s_idx+1].pos;

				g3_draw_htl_line(&v1, &v2);
			}
		}
	}	

	gr_set_cull(cull);
}

/**
 * Docking bay and fighter bay paths
 */
void model_draw_bay_paths(int model_num)
{
	int idx, s_idx;
	vec3d v1, v2;
	vertex l1, l2;	

	polymodel *pm = model_get(model_num);
	if(pm == NULL){
		return;
	}

	// render docking bay normals
	gr_set_color(0, 255, 0);
	for(idx=0; idx<pm->n_docks; idx++){
		for(s_idx=0; s_idx<pm->docking_bays[idx].num_slots; s_idx++){
			v1 = pm->docking_bays[idx].pnt[s_idx];
			vm_vec_scale_add(&v2, &v1, &pm->docking_bays[idx].norm[s_idx], 10.0f);

			// rotate the points
			g3_rotate_vertex(&l1, &v1);
			g3_rotate_vertex(&l2, &v2);

			// draw the point and normal
			g3_draw_sphere(&l1, 2.0);
			g3_draw_line(&l1, &l2);
		}
	}

	// render figher bay paths
	gr_set_color(0, 255, 255);
		
	// iterate through the paths that exist in the polymodel, searching for $bayN pathnames
	for (idx = 0; idx<pm->n_paths; idx++) {
		if ( !strnicmp(pm->paths[idx].name, NOX("$bay"), 4) ) {						
			for(s_idx=0; s_idx<pm->paths[idx].nverts-1; s_idx++){
				v1 = pm->paths[idx].verts[s_idx].pos;
				v2 = pm->paths[idx].verts[s_idx+1].pos;

				// rotate and draw
				g3_rotate_vertex(&l1, &v1);
				g3_rotate_vertex(&l2, &v2);
				g3_draw_line(&l1, &l2);
			}
		}
	}	
}


static const int MAX_ARC_SEGMENT_POINTS = 50;
int Num_arc_segment_points = 0;
vec3d Arc_segment_points[MAX_ARC_SEGMENT_POINTS];

extern int g3_draw_rod(int num_points, vec3d *vecs, float width, uint tmap_flags);

void interp_render_arc_segment( vec3d *v1, vec3d *v2, int depth )
{
	float d = vm_vec_dist_quick( v1, v2 );
	const float scaler = 0.30f;

	if ( (d < scaler) || (depth > 4) ) {
		// the real limit appears to be 33, so we should never hit this unless the code changes
		Assert( Num_arc_segment_points < MAX_ARC_SEGMENT_POINTS );

		memcpy( &Arc_segment_points[Num_arc_segment_points++], v2, sizeof(vec3d) );
	} else {
		// divide in half
		vec3d tmp;
		vm_vec_avg( &tmp, v1, v2 );
	
		tmp.xyz.x += (frand() - 0.5f) * d * scaler;
		tmp.xyz.y += (frand() - 0.5f) * d * scaler;
		tmp.xyz.z += (frand() - 0.5f) * d * scaler;

		// add additional point
		interp_render_arc_segment( v1, &tmp, depth+1 );
		interp_render_arc_segment( &tmp, v2, depth+1 );
	}
}

void interp_render_arc(vec3d *v1, vec3d *v2, color *primary, color *secondary, float arc_width)
{
	Num_arc_segment_points = 0;

	// need need to add the first point
	memcpy( &Arc_segment_points[Num_arc_segment_points++], v1, sizeof(vec3d) );

	// this should fill in all of the middle, and the last, points
	interp_render_arc_segment(v1, v2, 0);

	int tmap_flags = (TMAP_FLAG_RGB | TMAP_FLAG_GOURAUD | TMAP_HTL_3D_UNLIT | TMAP_FLAG_TRISTRIP);

	int mode = gr_zbuffer_set(GR_ZBUFF_READ);

	// use primary color for fist pass
	Assert( primary );
	gr_set_color_fast(primary);

	g3_draw_rod(Num_arc_segment_points, Arc_segment_points, arc_width, tmap_flags);

	if (secondary) {
		// now render again with a secondary center color
		gr_set_color_fast(secondary);

		g3_draw_rod(Num_arc_segment_points, Arc_segment_points, arc_width * 0.33f, tmap_flags);
	}

	gr_zbuffer_set(mode);
}

int Interp_lightning = 1;
DCF_BOOL( Arcs, Interp_lightning )

const int AR = 64;
const int AG = 64;
const int AB = 5;
const int AR2 = 128;
const int AG2 = 128;
const int AB2 = 10;

void interp_render_lightning( polymodel *pm, bsp_info * sm )
{
	int i;
	float width = 0.9f;
	color primary, secondary;

	Assert( sm->num_arcs > 0 );

	if (Interp_flags & MR_SHOW_OUTLINE_PRESET) {
		return;
	}

	if ( !Interp_lightning ) {
		return;
	}

	// try and scale the size a bit so that it looks equally well on smaller vessels
	if (pm->rad < 500.0f) {
		width *= (pm->rad * 0.01f);

		if (width < 0.2f)
			width = 0.2f;
	}

	for (i=0; i<sm->num_arcs; i++ )	{
		// pick a color based upon arc type
		switch(sm->arc_type[i]){
		// "normal", FreeSpace 1 style arcs
		case MARC_TYPE_NORMAL:
			if ( (rand()>>4) & 1 )	{
				gr_init_color(&primary, 64, 64, 255);
			} else {
				gr_init_color(&primary, 128, 128, 255);
			}

			gr_init_color(&secondary, 200, 200, 255);
			break;

		// "EMP" style arcs
		case MARC_TYPE_EMP:
			if ( (rand()>>4) & 1 )	{
				gr_init_color(&primary, AR, AG, AB);
			} else {
				gr_init_color(&primary, AR2, AG2, AB2);
			}

			gr_init_color(&secondary, 255, 255, 10);
			break;

		default:
			Int3();
		}

		// render the actual arc segment
		interp_render_arc( &sm->arc_pts[i][0], &sm->arc_pts[i][1], &primary, &secondary, width );
	}
}

void model_interp_subcall(polymodel * pm, int mn, int detail_level)
{
	int i;
	int zbuf_mode = gr_zbuffering_mode;

	if ( (mn < 0) || (mn>=pm->n_models) )
		return;

	Assert( mn >= 0 );
	Assert( mn < pm->n_models );

	if (pm->submodel[mn].blown_off){
		return;
	}

	if (pm->submodel[mn].is_thruster )	{
		if ( !(Interp_flags & MR_SHOW_THRUSTERS) ){
			return;
		}
		Interp_thrust_scale_subobj=1;
	} else {
		Interp_thrust_scale_subobj=0;
	}

	// Compute final submodel orientation by using the orientation matrix and the rotation angles.
	// By using this kind of computation, the rotational angles can always be computed relative
	// to the submodel itself, instead of relative to the parent - KeldorKatarn
	matrix rotation_matrix = pm->submodel[mn].orientation;
	vm_rotate_matrix_by_angles(&rotation_matrix, &pm->submodel[mn].angs);

	matrix inv_orientation;
	vm_copy_transpose_matrix(&inv_orientation, &pm->submodel[mn].orientation);

	matrix submodel_matrix;
	vm_matrix_x_matrix(&submodel_matrix, &rotation_matrix, &inv_orientation);

	g3_start_instance_matrix(&pm->submodel[mn].offset, &submodel_matrix, true);

	if ( !(Interp_flags & MR_NO_LIGHTING ) )	{
		light_rotate_all();
	}

	model_interp_sub( pm->submodel[mn].bsp_data, pm, &pm->submodel[mn], 0 );

	if (Interp_flags & MR_SHOW_PIVOTS )
		model_draw_debug_points( pm, &pm->submodel[mn] );

	if ( pm->submodel[mn].num_arcs )	{
		interp_render_lightning( pm, &pm->submodel[mn]);
	}

	i = pm->submodel[mn].first_child;
	while( i >= 0 )	{
		if (!pm->submodel[i].is_thruster )	{
			if(Interp_flags & MR_NO_ZBUFFER){
				zbuf_mode = GR_ZBUFF_NONE;
			} else {
				zbuf_mode = GR_ZBUFF_FULL;		// read only
			}

			gr_zbuffer_set(zbuf_mode);

			model_interp_subcall( pm, i, detail_level );
		}
		i = pm->submodel[i].next_sibling;
	}

	g3_done_instance(true);
}

// Returns one of the following
#define IBOX_ALL_OFF 0
#define IBOX_ALL_ON 1
#define IBOX_SOME_ON_SOME_OFF 2

int interp_box_offscreen( vec3d *min, vec3d *max )
{
	if ( keyd_pressed[KEY_LSHIFT] )	{
		return IBOX_ALL_ON;
	}

	vec3d v[8];
	v[0].xyz.x = min->xyz.x; v[0].xyz.y = min->xyz.y; v[0].xyz.z = min->xyz.z;
	v[1].xyz.x = max->xyz.x; v[1].xyz.y = min->xyz.y; v[1].xyz.z = min->xyz.z;
	v[2].xyz.x = max->xyz.x; v[2].xyz.y = max->xyz.y; v[2].xyz.z = min->xyz.z;
	v[3].xyz.x = min->xyz.x; v[3].xyz.y = max->xyz.y; v[3].xyz.z = min->xyz.z;

	v[4].xyz.x = min->xyz.x; v[4].xyz.y = min->xyz.y; v[4].xyz.z = max->xyz.z;
	v[5].xyz.x = max->xyz.x; v[5].xyz.y = min->xyz.y; v[5].xyz.z = max->xyz.z;
	v[6].xyz.x = max->xyz.x; v[6].xyz.y = max->xyz.y; v[6].xyz.z = max->xyz.z;
	v[7].xyz.x = min->xyz.x; v[7].xyz.y = max->xyz.y; v[7].xyz.z = max->xyz.z;

	ubyte and_codes = 0xff;
	ubyte or_codes = 0xff;
	int i;

	for (i=0; i<8; i++ )	{
		vertex tmp;
		ubyte codes=g3_rotate_vertex( &tmp, &v[i] );

		or_codes |= codes;
		and_codes &= codes;
	}

	// If and_codes is set this means that all points lie off to the
	// same side of the screen.
	if (and_codes)	{
		return IBOX_ALL_OFF;	//all points off screen
	}

	// If this is set it means at least one of the points is offscreen,
	// but they aren't all off to the same side.
	if (or_codes)	{
		return IBOX_SOME_ON_SOME_OFF;
	}

	// They are all onscreen.
	return IBOX_ALL_ON;	
}


/**
 * Calls the object interpreter to render an object.  
 * @return true if drawn
 */
int model_interp_sub(void *model_ptr, polymodel * pm, bsp_info *sm, int do_box_check )
{
	ubyte *p = (ubyte *)model_ptr;
	int chunk_type, chunk_size;
	int pushed = 0;

	chunk_type = w(p);
	chunk_size = w(p+4);

	
	while ( chunk_type != OP_EOF )	{

		switch (chunk_type) {
		case OP_EOF: return 1;
		case OP_DEFPOINTS:		model_interp_defpoints(p,pm,sm); break;
		case OP_FLATPOLY:		model_interp_flatpoly(p,pm); break;
		case OP_TMAPPOLY:		model_interp_tmappoly(p,pm); break;
		case OP_SORTNORM:		model_interp_sortnorm(p,pm,sm,do_box_check); break;
	
		case OP_BOUNDBOX:		

			if ( do_box_check )	{
				int retval = interp_box_offscreen( vp(p+8), vp(p+20) );
				switch( retval )	{
				case IBOX_ALL_OFF:
					goto DoneWithThis;	// Don't need to draw any more polys from this box
					break;

				case IBOX_ALL_ON:
					do_box_check = 0;		// Don't need to check boxes any more
					break;

				case IBOX_SOME_ON_SOME_OFF:
					// continue like we were
					break;
				default:
					Int3();
				}
			}

			if (Interp_flags & MR_SHOW_PIVOTS )	{
				#ifndef NDEBUG
				modelstats_num_boxes++;
				#endif
			}

			if ( !(Interp_flags & MR_NO_LIGHTING ) )	{
				if ( pushed )	{
					light_filter_pop();
					pushed = 0;

				}
				light_filter_push_box( vp(p+8), vp(p+20) );
				pushed = 1;
			}
			break;

		default:
			mprintf(( "Bad chunk type %d, len=%d in model_interp_sub\n", chunk_type, chunk_size ));
			Int3();		// Bad chunk type!
			return 0;
		}
		p += chunk_size;
		chunk_type = w(p);
		chunk_size = w(p+4);
	}

DoneWithThis:

	if ( !(Interp_flags & MR_NO_LIGHTING ) )	{
		if ( pushed )	{
			light_filter_pop();
			pushed = 0;
		}
	}

	return 1;
}


void model_render_shields( polymodel * pm )
{
	model_set_outline_color(255,255,255);
	int i, j;
	shield_tri *tri;
	vertex pnt0, tmp, prev_pnt;

	if ( Interp_flags & MR_SHOW_OUTLINE_PRESET )	{
		return;
	}

	gr_set_color(0, 0, 200 );

	//	Scan all the triangles in the mesh.
	for (i=0; i<pm->shield.ntris; i++ )	{

		tri = &pm->shield.tris[i];

		if (g3_check_normal_facing(&pm->shield.verts[tri->verts[0]].pos,&tri->norm ) ) {

			//	Process the vertices.
			//	Note this rotates each vertex each time it's needed, very dumb.
			for (j=0; j<3; j++ )	{

				g3_rotate_vertex(&tmp, &pm->shield.verts[tri->verts[j]].pos );

				if (j)
					g3_draw_line(&prev_pnt, &tmp);
				else
					pnt0 = tmp;
				prev_pnt = tmp;
			}

			g3_draw_line(&pnt0, &prev_pnt);
		}
	}
}

void model_render_insignias(polymodel *pm, int detail_level)
{
	// if the model has no insignias, or we don't have a texture, then bail
	if ( (pm->num_ins <= 0) || (Interp_insignia_bitmap < 0) )
		return;

	int idx, s_idx;
	vertex vecs[3];
	vertex *vlist[3] = { &vecs[0], &vecs[1], &vecs[2] };
	vec3d t1, t2, t3;
	int i1, i2, i3;
	int tmap_flags = TMAP_FLAG_TEXTURED | TMAP_FLAG_CORRECT | TMAP_HTL_3D_UNLIT;

	// set the proper texture	
	gr_set_bitmap(Interp_insignia_bitmap, GR_ALPHABLEND_NONE, GR_BITBLT_MODE_NORMAL, 0.65f);

	// otherwise render them	
	for(idx=0; idx<pm->num_ins; idx++){	
		// skip insignias not on our detail level
		if(pm->ins[idx].detail_level != detail_level){
			continue;
		}

		for(s_idx=0; s_idx<pm->ins[idx].num_faces; s_idx++){
			// get vertex indices
			i1 = pm->ins[idx].faces[s_idx][0];
			i2 = pm->ins[idx].faces[s_idx][1];
			i3 = pm->ins[idx].faces[s_idx][2];

			// transform vecs and setup vertices
			vm_vec_add(&t1, &pm->ins[idx].vecs[i1], &pm->ins[idx].offset);
			vm_vec_add(&t2, &pm->ins[idx].vecs[i2], &pm->ins[idx].offset);
			vm_vec_add(&t3, &pm->ins[idx].vecs[i3], &pm->ins[idx].offset);

			if(Cmdline_nohtl){
				g3_rotate_vertex(&vecs[0], &t1);
				g3_rotate_vertex(&vecs[1], &t2);
				g3_rotate_vertex(&vecs[2], &t3);
			}else{
				g3_transfer_vertex(&vecs[0], &t1);
				g3_transfer_vertex(&vecs[1], &t2);
				g3_transfer_vertex(&vecs[2], &t3);
			}

			// setup texture coords
			vecs[0].texture_position.u = pm->ins[idx].u[s_idx][0];
			vecs[0].texture_position.v = pm->ins[idx].v[s_idx][0];

			vecs[1].texture_position.u = pm->ins[idx].u[s_idx][1];
			vecs[1].texture_position.v = pm->ins[idx].v[s_idx][1];

			vecs[2].texture_position.u = pm->ins[idx].u[s_idx][2];
			vecs[2].texture_position.v = pm->ins[idx].v[s_idx][2];

			if (!Cmdline_nohtl) {
				light_apply_rgb( &vecs[0].r, &vecs[0].g, &vecs[0].b, &pm->ins[idx].vecs[i1], &pm->ins[idx].norm[i1], 1.5f );
				light_apply_rgb( &vecs[1].r, &vecs[1].g, &vecs[1].b, &pm->ins[idx].vecs[i2], &pm->ins[idx].norm[i2], 1.5f );
				light_apply_rgb( &vecs[2].r, &vecs[2].g, &vecs[2].b, &pm->ins[idx].vecs[i3], &pm->ins[idx].norm[i3], 1.5f );
				tmap_flags |= (TMAP_FLAG_RGB | TMAP_FLAG_GOURAUD);
			}

			// draw the polygon
			g3_draw_poly(3, vlist, tmap_flags);
		}
	}
}

int Model_texturing = 1;
int Model_polys = 1;

DCF_BOOL( model_texturing, Model_texturing )
DCF_BOOL( model_polys, Model_polys )

MONITOR( NumModelsRend )
MONITOR( NumHiModelsRend )
MONITOR( NumMedModelsRend )
MONITOR( NumLowModelsRend )

/**
 * Draws a bitmap with the specified 3d width & height 
 * @return 1 if off screen, 0 if not
 */
int model_get_rotated_bitmap_points(vertex *pnt,float angle, float rad, vertex *v)
{
	float sa, ca;
	int i;

	Assert( G3_count == 1 );
		
	sa = (float)sin(angle);
	ca = (float)cos(angle);

	float width, height;

	width = height = rad;

	v[0].world.xyz.x = (-width*ca - height*sa)*Matrix_scale.xyz.x + pnt->world.xyz.x;
	v[0].world.xyz.y = (-width*sa + height*ca)*Matrix_scale.xyz.y + pnt->world.xyz.y;
	v[0].world.xyz.z = pnt->world.xyz.z;
	v[0].screen.xyw.w = 0.0f;
	v[0].texture_position.u = 0.0f;
	v[0].texture_position.v = 0.0f;

	v[1].world.xyz.x = (width*ca - height*sa)*Matrix_scale.xyz.x + pnt->world.xyz.x;
	v[1].world.xyz.y = (width*sa + height*ca)*Matrix_scale.xyz.y + pnt->world.xyz.y;
	v[1].world.xyz.z = pnt->world.xyz.z;
	v[1].screen.xyw.w = 0.0f;
	v[1].texture_position.u = 1.0f;
	v[1].texture_position.v = 0.0f;

	v[2].world.xyz.x = (width*ca + height*sa)*Matrix_scale.xyz.x + pnt->world.xyz.x;
	v[2].world.xyz.y = (width*sa - height*ca)*Matrix_scale.xyz.y + pnt->world.xyz.y;
	v[2].world.xyz.z = pnt->world.xyz.z;
	v[2].screen.xyw.w = 0.0f;
	v[2].texture_position.u = 1.0f;
	v[2].texture_position.v = 1.0f;

	v[3].world.xyz.x = (-width*ca + height*sa)*Matrix_scale.xyz.x + pnt->world.xyz.x;
	v[3].world.xyz.y = (-width*sa - height*ca)*Matrix_scale.xyz.y + pnt->world.xyz.y;
	v[3].world.xyz.z = pnt->world.xyz.z;
	v[3].screen.xyw.w = 0.0f;
	v[3].texture_position.u = 0.0f;
	v[3].texture_position.v = 1.0f;

	ubyte codes_and=0xff;

	float sw,z;
	z = pnt->world.xyz.z - rad / 4.0f;
	if ( z < 0.0f ) z = 0.0f;
	sw = 1.0f / z;

	for (i=0; i<4; i++ )	{
		//now code the four points
		codes_and &= g3_code_vertex(&v[i]);
		v[i].flags = 0;		// mark as not yet projected
		g3_project_vertex(&v[i]);
		v[i].screen.xyw.w = sw;
	}

	if (codes_and)
		return 1;		//1 means off screen

	return 0;
}

float Interp_depth_scale = 1500.0f;

DCF(model_darkening,"Makes models darker with distance")
{
	if ( Dc_command )	{
		dc_get_arg(ARG_FLOAT);
		Interp_depth_scale = Dc_arg_float;
	}

	if ( Dc_help )	{
		dc_printf( "Usage: model_darkening float\n" );
		Dc_status = 0;	// don't print status if help is printed.  Too messy.
	}

	if ( Dc_status )	{
		dc_printf( "model_darkening = %.1f\n", Interp_depth_scale );
	}
}

void model_render(int model_num, matrix *orient, vec3d * pos, uint flags, int objnum, int lighting_skip, int *replacement_textures)
{
	int cull = 0;
	// replacement textures - Goober5000
	model_set_replacement_textures(replacement_textures);

	polymodel *pm = model_get(model_num);


	model_do_dumb_rotation(model_num);

	if (flags & MR_FORCE_CLAMP)
		gr_set_texture_addressing(TMAP_ADDRESS_CLAMP);

	int time = timestamp();
	for (int i = 0; i < pm->n_glow_point_banks; i++ ) { //glow point blink code -Bobboau
		glow_point_bank *bank = &pm->glow_point_banks[i];
		if (bank->glow_timestamp == 0)
			bank->glow_timestamp=time;
		if(bank->off_time){
			if(bank->is_on){
				if( (bank->on_time) > ((time - bank->disp_time) % (bank->on_time + bank->off_time)) ){
					bank->glow_timestamp=time;
					bank->is_on=0;
				}
			}else{
				if( (bank->off_time) < ((time - bank->disp_time) % (bank->on_time + bank->off_time)) ){
					bank->glow_timestamp=time;
					bank->is_on=1;
				}
			}
		}
	}

	// maybe turn off (hardware) culling
	if(flags & MR_NO_CULL){
		cull = gr_set_cull(0);
	}

	Interp_objnum = objnum;

	if ( flags & MR_NO_LIGHTING )	{
		Interp_light = 1.0f;
	} else if ( flags & MR_IS_ASTEROID ) {
		// Dim it based on distance
		float depth = vm_vec_dist_quick( pos, &Eye_position );
		if ( depth > Interp_depth_scale )	{
			Interp_light = Interp_depth_scale/depth;
			// If it is too far, exit
			if ( Interp_light < (1.0f/32.0f) ) {
				Interp_light = 0.0f;
				return;
			} else if ( Interp_light > 1.0f )	{
				Interp_light = 1.0f;
			}
		} else {
			Interp_light = 1.0f;
		}
	} else {
		Interp_light = 1.0f;
	}

	if ( !(flags & MR_NO_LIGHTING ) )	{
		light_filter_push( objnum, pos, pm->rad );
	}

	model_really_render(model_num, orient, pos, flags, objnum);

	if ( !(flags & MR_NO_LIGHTING ) )	{
		light_filter_pop();
	}

	// maybe turn culling back on
	if(flags & MR_NO_CULL){
		gr_set_cull(cull);
	}

	// turn off fog after each model renders
	if(The_mission.flags & MISSION_FLAG_FULLNEB){
		gr_fog_set(GR_FOGMODE_NONE, 0, 0, 0);
	}

	if (flags & MR_FORCE_CLAMP)
		gr_set_texture_addressing(TMAP_ADDRESS_WRAP);
}

// tmp_detail_level
// 0 - Max
// 1
// 2
// 3
// 4 - None

#if MAX_DETAIL_LEVEL != 4
#error MAX_DETAIL_LEVEL is assumed to be 4 in ModelInterp.cpp
#endif


/**
 * Find the distance from p0 to the closest point on a box.
 * The box's dimensions from 'min' to 'max'.
 */
float interp_closest_dist_to_box( vec3d *hitpt, vec3d *p0, vec3d *min, vec3d *max )
{
	float *origin = (float *)&p0->xyz.x;
	float *minB = (float *)min;
	float *maxB = (float *)max;
	float *coord = (float *)&hitpt->xyz.x;
	int inside = 1;
	int i;

	for (i=0; i<3; i++ )	{
		if ( origin[i] < minB[i] )	{
			coord[i] = minB[i];
			inside = 0;
		} else if (origin[i] > maxB[i] )	{
			coord[i] = maxB[i];
			inside = 0;
		} else {
			coord[i] = origin[i];
		}
	}
	
	if ( inside )	{
		return 0.0f;
	}

	return vm_vec_dist(hitpt,p0);
}


// Finds the closest point on a model to a point in space.  Actually only finds a point
// on the bounding box of the model.    
// Given:
//   model_num      Which model
//   orient         Orientation of the model
//   pos            Position of the model
//   eye_pos        Point that you want to find the closest point to
// Returns:
//   distance from eye_pos to closest_point.  0 means eye_pos is 
//   on or inside the bounding box.
//   Also fills in outpnt with the actual closest point.
float model_find_closest_point( vec3d *outpnt, int model_num, int submodel_num, matrix *orient, vec3d * pos, vec3d *eye_pos )
{
	vec3d closest_pos, tempv, eye_rel_pos;
	
	polymodel *pm = model_get(model_num);

	if ( submodel_num < 0 )	{
		 submodel_num = pm->detail[0];
	}

	// Rotate eye pos into object coordinates
	vm_vec_sub(&tempv,pos,eye_pos );
	vm_vec_rotate(&eye_rel_pos,&tempv,orient );

	return interp_closest_dist_to_box( &closest_pos, &eye_rel_pos, &pm->submodel[submodel_num].min, &pm->submodel[submodel_num].max );
}

int tiling = 1;
DCF(tiling, "")
{
	tiling = !tiling;
	if(tiling){
		dc_printf("Tiled textures\n");
	} else {
		dc_printf("Non-tiled textures\n");
	}
}

void moldel_calc_facing_pts( vec3d *top, vec3d *bot, vec3d *fvec, vec3d *pos, float w, float z_add, vec3d *Eyeposition )
{
	vec3d uvec, rvec;
	vec3d temp;

	temp = *pos;

	vm_vec_sub( &rvec, Eyeposition, &temp );
	vm_vec_normalize( &rvec );	

	vm_vec_crossprod(&uvec,fvec,&rvec);
	vm_vec_normalize(&uvec);

	vm_vec_scale_add( top, &temp, &uvec, w/2.0f );
	vm_vec_scale_add( bot, &temp, &uvec, -w/2.0f );	
}

extern bool Scene_framebuffer_in_frame;

/**
 * Maybe draw mode thruster glows
 */
void model_render_thrusters(polymodel *pm, int objnum, ship *shipp, matrix *orient, vec3d *pos)
{
	int i, j;
	int n_q = 0;
	size_t 	k;
	vec3d norm, norm2, fvec, pnt, npnt;
	thruster_bank *bank = NULL;
	vertex p;
	bool do_render = false;

	if ( pm == NULL ) {
		Int3();
		return;
	}

	if ( !(Interp_flags & MR_SHOW_THRUSTERS) ) {
		return;
	}

	// get an initial count to figure out how man geo batchers we need allocated
	for (i = 0; i < pm->n_thrusters; i++ ) {
		bank = &pm->thrusters[i];
		n_q += bank->num_points;
	}

	if (n_q <= 0) {
		return;
	}

	// primary_thruster_batcher
	if (Interp_thrust_glow_bitmap >= 0) {
		do_render = true;
	}

	// secondary_thruster_batcher
	if (Interp_secondary_thrust_glow_bitmap >= 0) {
		do_render = true;
	}

	// tertiary_thruster_batcher
	if (Interp_tertiary_thrust_glow_bitmap >= 0) {
		do_render = true;
	}

	if (do_render == false) {
		return;
	}

	// this is used for the secondary thruster glows 
	// it only needs to be calculated once so I'm doing it here -Bobboau
	norm.xyz.z = -1.0f;
	norm.xyz.x = 1.0f;
	norm.xyz.y = -1.0f;
	norm.xyz.x *= Interp_thrust_rotvel.xyz.y/2;
	norm.xyz.y *= Interp_thrust_rotvel.xyz.x/2;
	vm_vec_normalize(&norm);

	// we need to disable fogging
	if (The_mission.flags & MISSION_FLAG_FULLNEB)
		gr_fog_set(GR_FOGMODE_NONE, 0, 0, 0);

	for (i = 0; i < pm->n_thrusters; i++ ) {
		vec3d submodel_static_offset; // The associated submodel's static offset in the ship's frame of reference
		bool submodel_rotation = false;

		bank = &pm->thrusters[i];

		// don't draw this thruster if the engine is destroyed or just not on
		if ( !model_should_render_engine_glow(objnum, bank->obj_num) )
			continue;

		// If bank is attached to a submodel, prepare to account for rotations
		//
		// TODO: This won't work in the ship lab, because the lab code doesn't
		// set the the necessary submodel instance info needed here. The second
		// condition is thus a hack to disable the feature while in the lab, and
		// can be removed if the lab is re-structured accordingly. -zookeeper
		if ( bank->submodel_num > -1 && pm->submodel[bank->submodel_num].can_move && (gameseq_get_state_idx(GS_STATE_LAB) == -1) ) {
			model_find_submodel_offset(&submodel_static_offset, Ship_info[shipp->ship_info_index].model_num, bank->submodel_num);

			submodel_rotation = true;
		}

		for (j = 0; j < bank->num_points; j++) {
			Assert( bank->points != NULL );

			float d, D;
			vec3d tempv;
			glow_point *gpt = &bank->points[j];
			vec3d loc_offset = gpt->pnt;
			vec3d loc_norm = gpt->norm;
			vec3d world_pnt;
			vec3d world_norm;

			if ( submodel_rotation ) {
				vm_vec_sub(&loc_offset, &gpt->pnt, &submodel_static_offset);

				tempv = loc_offset;
				find_submodel_instance_point_normal(&loc_offset, &loc_norm, &Objects[objnum], bank->submodel_num, &tempv, &loc_norm);
			}

			vm_vec_unrotate(&world_pnt, &loc_offset, orient);
			vm_vec_add2(&world_pnt, pos);

			if (shipp) {
				// if ship is warping out, check position of the engine glow to the warp plane
				if ( (shipp->flags & (SF_ARRIVING) ) && (shipp->warpin_effect) && Ship_info[shipp->ship_info_index].warpin_type != WT_HYPERSPACE) {
					vec3d warp_pnt, tmp;
					matrix warp_orient;

					shipp->warpin_effect->getWarpPosition(&warp_pnt);
					shipp->warpin_effect->getWarpOrientation(&warp_orient);
					vm_vec_sub( &tmp, &world_pnt, &warp_pnt );

					if ( vm_vec_dot( &tmp, &warp_orient.vec.fvec ) < 0.0f ) {
						break;
					}
				}

				if ( (shipp->flags & (SF_DEPART_WARP) ) && (shipp->warpout_effect) && Ship_info[shipp->ship_info_index].warpout_type != WT_HYPERSPACE) {
					vec3d warp_pnt, tmp;
					matrix warp_orient;

					shipp->warpout_effect->getWarpPosition(&warp_pnt);
					shipp->warpout_effect->getWarpOrientation(&warp_orient);
					vm_vec_sub( &tmp, &world_pnt, &warp_pnt );

					if ( vm_vec_dot( &tmp, &warp_orient.vec.fvec ) > 0.0f ) {
						break;
					}
				}
			}

			vm_vec_sub(&tempv, &View_position, &world_pnt);
			vm_vec_normalize(&tempv);
			vm_vec_unrotate(&world_norm, &loc_norm, orient);
			D = d = vm_vec_dot(&tempv, &world_norm);

			// ADAM: Min throttle draws rad*MIN_SCALE, max uses max.
			#define NOISE_SCALE 0.5f
			#define MIN_SCALE 3.4f
			#define MAX_SCALE 4.7f

			float magnitude;
			vec3d scale_vec = { { { 1.0f, 0.0f, 0.0f } } };

			// normalize banks, in case of incredibly big normals
			if ( !IS_VEC_NULL_SQ_SAFE(&world_norm) )
				vm_vec_copy_normalize(&scale_vec, &world_norm);

			// adjust for thrust
			(scale_vec.xyz.x *= Interp_thrust_scale_x) -= 0.1f;
			(scale_vec.xyz.y *= Interp_thrust_scale_y) -= 0.1f;
			(scale_vec.xyz.z *= Interp_thrust_scale)   -= 0.1f;

			// get magnitude, which we will use as the scaling reference
			magnitude = vm_vec_normalize(&scale_vec);

			// get absolute value
			if (magnitude < 0.0f)
				magnitude *= -1.0f;

			float scale = magnitude * (MAX_SCALE - MIN_SCALE) + MIN_SCALE;

			if (d > 0.0f){
				// Make glow bitmap fade in/out quicker from sides.
				d *= 3.0f;

				if (d > 1.0f)
					d = 1.0f;
			}

			float fog_int = 1.0f;

			// fade them in the nebula as well
			if (The_mission.flags & MISSION_FLAG_FULLNEB) {
				vm_vec_rotate(&npnt, &gpt->pnt, orient);
				vm_vec_add2(&npnt, pos);

				fog_int = (1.0f - (neb2_get_fog_intensity(&npnt)));

				if (fog_int > 1.0f)
					fog_int = 1.0f;

				d *= fog_int;

				if (d > 1.0f)
					d = 1.0f;
			}

			float w = gpt->radius * (scale + Interp_thrust_glow_noise * NOISE_SCALE);

			// these lines are used by the tertiary glows, thus we will need to project this all of the time
			if (Cmdline_nohtl) {
				g3_rotate_vertex( &p, &world_pnt );
			} else {
				g3_transfer_vertex( &p, &world_pnt );
			}

			// start primary thruster glows
			if ( (Interp_thrust_glow_bitmap >= 0) && (d > 0.0f) ) {
				p.r = p.g = p.b = p.a = (ubyte)(255.0f * d);
				batch_add_bitmap(
					Interp_thrust_glow_bitmap, 
					TMAP_FLAG_GOURAUD | TMAP_FLAG_RGB | TMAP_FLAG_TEXTURED | TMAP_HTL_3D_UNLIT | TMAP_FLAG_SOFT_QUAD, 
					&p,
					0,
					(w * 0.5f * Interp_thrust_glow_rad_factor),
					1.0f,
					(w * 0.325f)
				);
			}

			// start tertiary thruster glows
			if (Interp_tertiary_thrust_glow_bitmap >= 0) {
				p.screen.xyw.w -= w;
				p.r = p.g = p.b = p.a = (ubyte)(255.0f * fog_int);
				batch_add_bitmap_rotated(
					Interp_tertiary_thrust_glow_bitmap,
					TMAP_FLAG_GOURAUD | TMAP_FLAG_RGB | TMAP_FLAG_TEXTURED | TMAP_HTL_3D_UNLIT | TMAP_FLAG_SOFT_QUAD,
					&p,
					(magnitude * 4),
					(w * 0.6f * Interp_tertiary_thrust_glow_rad_factor),
					1.0f,
					(-(D > 0) ? D : -D)
				);
			}

			// begin secondary glows
			if (Interp_secondary_thrust_glow_bitmap >= 0) {
				pnt = world_pnt;
				scale = magnitude * (MAX_SCALE - (MIN_SCALE / 2)) + (MIN_SCALE / 2);
				vm_vec_unrotate(&world_norm, &norm, orient);
				d = vm_vec_dot(&tempv, &world_norm);
				d += 0.75f;
				d *= 3.0f;

				if (d > 1.0f)
					d = 1.0f;

				if (d > 0.0f) {
					vm_vec_add(&norm2, &world_norm, &pnt);
					vm_vec_sub(&fvec, &norm2, &pnt);
					vm_vec_normalize(&fvec);

					float wVal = gpt->radius * scale * 2;

					vm_vec_scale_add(&norm2, &pnt, &fvec, wVal * 2 * Interp_thrust_glow_len_factor);

					if (The_mission.flags & MISSION_FLAG_FULLNEB) {
						vm_vec_add(&npnt, &pnt, pos);
						d *= fog_int;
					}

					batch_add_beam(Interp_secondary_thrust_glow_bitmap,
							TMAP_FLAG_GOURAUD | TMAP_FLAG_RGB | TMAP_FLAG_TEXTURED | TMAP_FLAG_CORRECT | TMAP_HTL_3D_UNLIT,
							&pnt, &norm2, wVal*Interp_secondary_thrust_glow_rad_factor*0.5f, d
					);
					if (Scene_framebuffer_in_frame && Interp_draw_distortion) {
						vm_vec_scale_add(&norm2, &pnt, &fvec, wVal * 2 * Interp_distortion_thrust_length_factor);
						int dist_bitmap;
						if (Interp_distortion_thrust_bitmap > 0) {
							dist_bitmap = Interp_distortion_thrust_bitmap;
						}
						else {
							dist_bitmap = Interp_secondary_thrust_glow_bitmap;
						}
						float mag = vm_vec_mag(&gpt->pnt); 
						mag -= (float)((int)mag);//Valathil - Get a fairly random but constant number to offset the distortion texture
						distortion_add_beam(dist_bitmap,
							TMAP_FLAG_GOURAUD | TMAP_FLAG_RGB | TMAP_FLAG_TEXTURED | TMAP_FLAG_CORRECT | TMAP_HTL_3D_UNLIT | TMAP_FLAG_DISTORTION_THRUSTER | TMAP_FLAG_SOFT_QUAD,
							&pnt, &norm2, wVal*Interp_distortion_thrust_rad_factor*0.5f, 1.0f, mag
						);
					}
				}
			}

			// begin particles
			if (shipp) {
				ship_info *sip = &Ship_info[shipp->ship_info_index];
				particle_emitter pe;
				thruster_particles *tp;
				size_t num_particles = 0;

				if (Interp_afterburner)
					num_particles = sip->afterburner_thruster_particles.size();
				else
					num_particles = sip->normal_thruster_particles.size();

				for (k = 0; k < num_particles; k++) {
					if (Interp_afterburner)
						tp = &sip->afterburner_thruster_particles[k];
					else
						tp = &sip->normal_thruster_particles[k];

					float v = vm_vec_mag_quick(&Objects[shipp->objnum].phys_info.desired_vel);

					vm_vec_unrotate(&npnt, &gpt->pnt, orient);
					vm_vec_add2(&npnt, pos);

					// Where the particles emit from
					pe.pos = npnt;
					// Initial velocity of all the particles
					pe.vel = Objects[shipp->objnum].phys_info.desired_vel;
					pe.min_vel = v * 0.75f;
					pe.max_vel =  v * 1.25f;
					// What normal the particle emit around
					pe.normal = orient->vec.fvec;
					vm_vec_negate(&pe.normal);

					// Lowest number of particles to create
					pe.num_low = tp->n_low;
					// Highest number of particles to create
					pe.num_high = tp->n_high;
					pe.min_rad = gpt->radius * tp->min_rad;
					pe.max_rad = gpt->radius * tp->max_rad;
					// How close they stick to that normal 0=on normal, 1=180, 2=360 degree
					pe.normal_variance = tp->variance;

					particle_emit( &pe, PARTICLE_BITMAP, tp->thruster_bitmap.first_frame);
				}
			}
		}
	}

	// save current zbuffer, and set the correct mode for us
	int zbuff_save = gr_zbuffering_mode;
	gr_zbuffer_set(GR_ZBUFF_READ);
	gr_zbuffer_set(zbuff_save);
}

void model_render_glow_points(polymodel *pm, ship *shipp, matrix *orient, vec3d *pos, bool use_depth_buffer = true)
{
	int i, j;

	int cull = gr_set_cull(0);

	for (i = 0; i < pm->n_glow_point_banks; i++ ) {
		glow_point_bank *bank = &pm->glow_point_banks[i];

		if (pm->submodel[bank->submodel_parent].blown_off)
			continue;

		if (bank->is_on) {
			if ( (shipp != NULL) && !(shipp->glow_point_bank_active[i]) )
				continue;

			for (j = 0; j < bank->num_points; j++) {
				Assert( bank->points != NULL );
				int flick;

				if (pm->submodel[pm->detail[0]].num_arcs) {
					flick = static_rand( timestamp() % 20 ) % (pm->submodel[pm->detail[0]].num_arcs + j); //the more damage, the more arcs, the more likely the lights will fail
				} else {
					flick = 1;
				}

				if (flick == 1) {
					glow_point *gpt = &bank->points[j];
                    vec3d loc_offset = gpt->pnt;
                    vec3d loc_norm = gpt->norm;
                    vec3d world_pnt;
                    vec3d world_norm;
                    vec3d tempv;
                    vec3d submodel_static_offset; // The associated submodel's static offset in the ship's frame of reference
                    bool submodel_rotation = false;

					if ( bank->submodel_parent > 0 && pm->submodel[bank->submodel_parent].can_move && (gameseq_get_state_idx(GS_STATE_LAB) == -1) && shipp != NULL ) {
						model_find_submodel_offset(&submodel_static_offset, Ship_info[shipp->ship_info_index].model_num, bank->submodel_parent);

						submodel_rotation = true;
					}

					if ( submodel_rotation ) {
						vm_vec_sub(&loc_offset, &gpt->pnt, &submodel_static_offset);

						tempv = loc_offset;
						find_submodel_instance_point_normal(&loc_offset, &loc_norm, &Objects[shipp->objnum], bank->submodel_parent, &tempv, &loc_norm);
					}

					vm_vec_unrotate(&world_pnt, &loc_offset, orient);
					vm_vec_add2(&world_pnt, pos);

					vm_vec_unrotate(&world_norm, &loc_norm, orient);
					
					if ( (shipp != NULL) && (shipp->flags & (SF_ARRIVING | SF_DEPART_WARP) ) && (shipp->warpin_effect) && Ship_info[shipp->ship_info_index].warpin_type != WT_HYPERSPACE) {
						if (g3_point_behind_user_plane(&world_pnt))
							continue;
					}

					switch (bank->type)
					{
						case 0:
						{
							float d;

							if ( IS_VEC_NULL(&world_norm) ) {
								d = 1.0f;	//if given a nul vector then always show it
							} else {
								vm_vec_sub(&tempv,&View_position,&world_pnt);
								vm_vec_normalize(&tempv);

								d = vm_vec_dot(&tempv,&world_norm);
								d -= 0.25;	
							}
					
							if (d > 0.0f) {
								vertex p;

								d *= 3.0f;

								if (d > 1.0f)
									d = 1.0f;

								float w = gpt->radius;

								// fade them in the nebula as well
								if (The_mission.flags & MISSION_FLAG_FULLNEB) {
									//vec3d npnt;
									//vm_vec_add(&npnt, &loc_offset, pos);

									d *= (1.0f - neb2_get_fog_intensity(&world_pnt));
									w *= 1.5;	//make it bigger in a nebula
								}
				
								// disable fogging
								if (Interp_tmap_flags & TMAP_FLAG_PIXEL_FOG)
									gr_fog_set(GR_FOGMODE_NONE, 0, 0, 0);
	
								if (!Cmdline_nohtl) {
									g3_transfer_vertex(&p, &world_pnt);
								} else {
									g3_rotate_vertex(&p, &world_pnt);
								}
 
								p.r = p.g = p.b = p.a = (ubyte)(255.0f * d);
								int gpflags = TMAP_FLAG_GOURAUD | TMAP_FLAG_RGB | TMAP_FLAG_TEXTURED | TMAP_HTL_3D_UNLIT;
								if (use_depth_buffer)
									gpflags |= TMAP_FLAG_SOFT_QUAD;

								batch_add_bitmap(
									bank->glow_bitmap,
									gpflags,  
									&p,
									0,
									(w * 0.5f),
									d
								);
							} //d>0.0f

							break;
						}

						case 1:
						{
							vertex verts[4];
							vec3d fvec, top1, bottom1, top2, bottom2, start, end;

							vm_vec_add2(&loc_norm, &loc_offset);

							vm_vec_rotate(&start, &loc_offset, orient);
							vm_vec_rotate(&end, &loc_norm, orient);
							vm_vec_sub(&fvec, &end, &start);

							vm_vec_normalize(&fvec);

							moldel_calc_facing_pts(&top1, &bottom1, &fvec, &loc_offset, gpt->radius, 1.0f, &View_position);
							moldel_calc_facing_pts(&top2, &bottom2, &fvec, &loc_norm, gpt->radius, 1.0f, &View_position);

							int idx = 0;

							if (Cmdline_nohtl) {
								g3_rotate_vertex(&verts[0], &bottom1);
								g3_rotate_vertex(&verts[1], &bottom2);
								g3_rotate_vertex(&verts[2], &top2);
								g3_rotate_vertex(&verts[3], &top1);

								for (idx = 0; idx < 4; idx++) {
									g3_project_vertex(&verts[idx]);
								}
							} else {
								g3_transfer_vertex(&verts[0], &bottom1);
								g3_transfer_vertex(&verts[1], &bottom2);
								g3_transfer_vertex(&verts[2], &top2);
								g3_transfer_vertex(&verts[3], &top1);
							}

							verts[0].texture_position.u = 0.0f;
							verts[0].texture_position.v = 0.0f;

							verts[1].texture_position.u = 1.0f;
							verts[1].texture_position.v = 0.0f;

							verts[2].texture_position.u = 1.0f;
							verts[2].texture_position.v = 1.0f;

							verts[3].texture_position.u = 0.0f;
							verts[3].texture_position.v = 1.0f;

							vm_vec_sub(&tempv,&View_position,&loc_offset);
							vm_vec_normalize(&tempv);

							if (The_mission.flags & MISSION_FLAG_FULLNEB) {
								gr_set_bitmap(bank->glow_neb_bitmap, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, 1.0f);		
							} else {
								gr_set_bitmap(bank->glow_bitmap, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, 1.0f);		
							}

							gr_render(4, verts, TMAP_FLAG_TILED | TMAP_FLAG_TEXTURED | TMAP_FLAG_CORRECT | TMAP_HTL_3D_UNLIT);

							break;
						}
					} // switch(bank->type)
				} // flick
			} // for slot
		} // bank is on
	} // for bank

	gr_set_cull(cull);
}

void light_set_all_relevent();
extern int Warp_model;

void model_really_render(int model_num, matrix *orient, vec3d * pos, uint flags, int objnum )
{
	int i;
	int cull = 1;
	polymodel * pm;

	uint save_gr_zbuffering_mode;
	int zbuf_mode;
	ship *shipp = NULL;
	object *objp = NULL;
	bool set_autocen = false;
	bool draw_thrusters = false;

	// just to be on the safe side
	Assert( Interp_objnum == objnum );

	if (objnum >= 0) {
		objp = &Objects[objnum];

		if (objp->type == OBJ_SHIP) {
			shipp = &Ships[objp->instance];

			if (shipp->flags2 & SF2_GLOWMAPS_DISABLED)
				flags |= MR_NO_GLOWMAPS;
		}
	}

	if (FULLCLOAK != 1)
		model_interp_sortnorm = model_interp_sortnorm_b2f;


	MONITOR_INC( NumModelsRend, 1 );	

	Interp_orient = orient;
	Interp_pos = pos;

	int tmp_detail_level = Game_detail_level;

	// Turn off engine effect
	Interp_thrust_scale_subobj=0;

	if (!Model_texturing)
		flags |= MR_NO_TEXTURING;

	if ( !Model_polys )	{
		flags |= MR_NO_POLYS;
	}

	Interp_flags = flags;			 

	pm = model_get(model_num);	

	// Set the flags we will pass to the tmapper
	Interp_tmap_flags = TMAP_FLAG_GOURAUD | TMAP_FLAG_RGB;

	// if we're in nebula mode, fog everything except for the warp holes and other non-fogged models
	if((The_mission.flags & MISSION_FLAG_FULLNEB) && (Neb2_render_mode != NEB2_RENDER_NONE) && !(flags & MR_NO_FOGGING)){
		Interp_tmap_flags |= TMAP_FLAG_PIXEL_FOG;
	}

	if ( !(Interp_flags & MR_NO_TEXTURING) )	{
		Interp_tmap_flags |= TMAP_FLAG_TEXTURED;

		if ( (pm->flags & PM_FLAG_ALLOW_TILING) && tiling)
			Interp_tmap_flags |= TMAP_FLAG_TILED;

		if ( !(Interp_flags & MR_NO_CORRECT) )	{
			Interp_tmap_flags |= TMAP_FLAG_CORRECT;
		}
	}

	if ( Interp_flags & MR_ANIMATED_SHADER )
		Interp_tmap_flags |= TMAP_ANIMATED_SHADER;

	if ( Interp_desaturate ) {
		Interp_tmap_flags |= TMAP_FLAG_DESATURATE;
	}

	save_gr_zbuffering_mode = gr_zbuffering_mode;
	zbuf_mode = gr_zbuffering_mode;

	if (!(Game_detail_flags & DETAIL_FLAG_MODELS) )	{
		gr_set_color(0,128,0);
		g3_draw_sphere_ez( pos, pm->rad );
		return;
	}

	bool is_outlines_only = (flags & MR_NO_POLYS) && ((flags & MR_SHOW_OUTLINE_PRESET) || (flags & MR_SHOW_OUTLINE));
	bool is_outlines_only_htl = !Cmdline_nohtl && (flags & MR_NO_POLYS) && (flags & MR_SHOW_OUTLINE_HTL);
	bool use_api = !is_outlines_only_htl || (gr_screen.mode == GR_OPENGL);

	g3_start_instance_matrix(pos, orient, use_api);

	if ( Interp_flags & MR_SHOW_RADIUS )	{
		if ( !(Interp_flags & MR_SHOW_OUTLINE_PRESET) )	{
			gr_set_color(0,64,0);
			g3_draw_sphere_ez(&vmd_zero_vector,pm->rad);
		}
	}

	Assert( pm->n_detail_levels < MAX_MODEL_DETAIL_LEVELS );

	vec3d closest_pos;
	float depth = model_find_closest_point( &closest_pos, model_num, -1, orient, pos, &Eye_position );
	if ( pm->n_detail_levels > 1 )	{

		if ( Interp_flags & MR_LOCK_DETAIL )	{
			i = Interp_detail_level_locked+1;
		} else {

			#if MAX_DETAIL_LEVEL != 4
			#error Code in modelInterp.cpp assumes MAX_DETAIL_LEVEL == 4
			#endif

			switch( Detail.detail_distance )	{
			case 0:		// lowest
				depth /= The_mission.ai_profile->detail_distance_mult[0];
				break;
			case 1:		// lower than normal
				depth /= The_mission.ai_profile->detail_distance_mult[1];
				break;
			case 2:		// default
				depth /= The_mission.ai_profile->detail_distance_mult[2];
				break;
			case 3:		// above normal
				depth /= The_mission.ai_profile->detail_distance_mult[3];
				break;
			case 4:		// even more normal
				depth /= The_mission.ai_profile->detail_distance_mult[4];
				break;
			}

			// nebula ?
			if(The_mission.flags & MISSION_FLAG_FULLNEB){
				depth *= neb2_get_lod_scale(Interp_objnum);
			}

			for ( i=0; i<pm->n_detail_levels; i++ )	{
				if ( depth<=pm->detail_depth[i] ){
					break;
				}
			}

			// If no valid detail depths specified, use highest.
			if ( (i > 1) && (pm->detail_depth[i-1] < 1.0f))	{
				i = 1;
			}
		}

		// maybe force lower detail
		if (Interp_flags & MR_FORCE_LOWER_DETAIL) {
			i++;
		}

		Interp_detail_level = i-1-tmp_detail_level;

		if ( Interp_detail_level < 0 ) 
			Interp_detail_level = 0;
		else if (Interp_detail_level >= pm->n_detail_levels ) 
			Interp_detail_level = pm->n_detail_levels-1;

	} else {
		Interp_detail_level = 0;
	}

#ifndef NDEBUG
	if ( Interp_detail_level == 0 )	{
		MONITOR_INC( NumHiModelsRend, 1 );
	} else if ( Interp_detail_level == pm->n_detail_levels-1 ) {
		MONITOR_INC( NumLowModelsRend, 1 );
	}  else {
		MONITOR_INC( NumMedModelsRend, 1 );
	}
#endif

	// scale the render box settings based on the "Model Detail" slider
	switch (Detail.detail_distance)
	{
		// 1st dot is 20%
		case 0:
			Interp_box_scale = 0.2f;
			break;

		// 2nd dot is 50%
		case 1:
			Interp_box_scale = 0.5f;
			break;

		// 3rd dot is 80%
		case 2:
			Interp_box_scale = 0.8f;
			break;

		// 4th dot is 100% (this is the default setting for "High" and "Very High" settings)
		case 3:
			Interp_box_scale = 1.0f;
			break;

		// 5th dot (max) is 120%
		case 4:
			Interp_box_scale = 1.2f;
			break;
	}

	vec3d auto_back = ZERO_VECTOR;
	if (Interp_flags & MR_AUTOCENTER) {
		// standard autocenter using data in model
		if (pm->flags & PM_FLAG_AUTOCEN) {
			auto_back = pm->autocenter;
			vm_vec_scale(&auto_back, -1.0f);
			set_autocen = true;
		}
		// fake autocenter if we are a missile and don't already have autocen info
		else if (Interp_flags & MR_IS_MISSILE) {
            auto_back.xyz.x = -( (pm->submodel[pm->detail[Interp_detail_level]].max.xyz.x + pm->submodel[pm->detail[Interp_detail_level]].min.xyz.x) / 2.0f );
            auto_back.xyz.y = -( (pm->submodel[pm->detail[Interp_detail_level]].max.xyz.y + pm->submodel[pm->detail[Interp_detail_level]].min.xyz.y) / 2.0f );
			auto_back.xyz.z = -( (pm->submodel[pm->detail[Interp_detail_level]].max.xyz.z + pm->submodel[pm->detail[Interp_detail_level]].min.xyz.z) / 2.0f );
			set_autocen = true;
		}

		if (set_autocen)
			g3_start_instance_matrix(&auto_back, NULL, true);
	}

	gr_zbias(1);

	if(Interp_tmap_flags & TMAP_FLAG_PIXEL_FOG)
	{
		float fog_near = 10.0f, fog_far = 1000.0f;
		neb2_get_adjusted_fog_values(&fog_near, &fog_far, objp);
		unsigned char r, g, b;
		neb2_get_fog_color(&r, &g, &b);
		gr_fog_set(GR_FOGMODE_FOG, r, g, b, fog_near, fog_far);
	}

	if (is_outlines_only_htl) {
		gr_set_fill_mode( GR_FILL_MODE_WIRE );
		gr_set_color_fast( &Interp_outline_color );
		// lines shouldn't be rendered with textures or special RGB colors (assuming preset colors)
		Interp_flags |= MR_NO_TEXTURING;
		Interp_tmap_flags &= ~TMAP_FLAG_TEXTURED;
		Interp_tmap_flags &= ~TMAP_FLAG_RGB;
		// don't render with lighting either
		Interp_flags |= MR_NO_LIGHTING;
	} else {
		gr_set_fill_mode( GR_FILL_MODE_SOLID );
	}

	if ( (Interp_flags & MR_NO_ZBUFFER) || (Interp_flags & MR_ALL_XPARENT) ) {
		zbuf_mode = GR_ZBUFF_NONE;
	} else {
		zbuf_mode = GR_ZBUFF_FULL;
	}

	gr_zbuffer_set(zbuf_mode);

	if (Interp_flags & MR_EDGE_ALPHA) {
		gr_center_alpha(-1);
	} else if (Interp_flags & MR_CENTER_ALPHA) {
		gr_center_alpha(1);
	} else {
		gr_center_alpha(0);
	}

	if ( (Interp_flags & MR_NO_CULL) || (Interp_flags & MR_ALL_XPARENT) || (Interp_warp_bitmap >= 0) ) {
		cull = gr_set_cull(0);
	} else {
		cull = gr_set_cull(1);
	}

	// Goober5000
	Interp_base_frametime = 0;

	if ( (objp != NULL) && (objp->type == OBJ_SHIP) ) {
		Interp_base_frametime = Ships[objp->instance].base_texture_anim_frametime;
	}

	if ( !(Interp_flags & MR_NO_LIGHTING) ) {
		gr_set_lighting(true, true);
	}

	// rotate lights
	if ( !(Interp_flags & MR_NO_LIGHTING) )	{
		light_rotate_all();
 
		if ( !Cmdline_nohtl ) {
			light_set_all_relevent();
		}
	}
	if ( !(Interp_flags & MR_NO_LIGHTING) && (is_outlines_only_htl || (!Cmdline_nohtl && !is_outlines_only)) ) {
		opengl_change_active_lights(0); // Set up OpenGl lighting;
	}

	if (is_outlines_only_htl || (!Cmdline_nohtl && !is_outlines_only)) {
		gr_set_buffer(pm->vertex_buffer_id);
	}

	// Draw the subobjects	
	i = pm->submodel[pm->detail[Interp_detail_level]].first_child;

	while( i >= 0 )	{
		if ( !pm->submodel[i].is_thruster ) {
			// When in htl mode render with htl method unless its a jump node
			if (is_outlines_only_htl || (!Cmdline_nohtl && !is_outlines_only)) {
				model_render_children_buffers( pm, i, Interp_detail_level );
			} else {
				model_interp_subcall( pm, i, Interp_detail_level );
			}
		} else {
			draw_thrusters = true;
		}

		i = pm->submodel[i].next_sibling;
	}	

	gr_zbias(0);	

	model_radius = pm->submodel[pm->detail[Interp_detail_level]].rad;

	//*************************** draw the hull of the ship *********************************************

	// When in htl mode render with htl method unless its a jump node
	if (is_outlines_only_htl || (!Cmdline_nohtl && !is_outlines_only)) {
		transparent_submodel ts;
		ts.is_submodel = false;
		ts.pop_matrix = false;
		transparent_submodels.push_back(ts);
		model_render_buffers(pm, pm->detail[Interp_detail_level]);
	} else {
		model_interp_subcall(pm, pm->detail[Interp_detail_level], Interp_detail_level);
	}

	// Draw the thruster subobjects
	if (draw_thrusters) {
		i = pm->submodel[pm->detail[Interp_detail_level]].first_child;

		while( i >= 0 ) {
			if (pm->submodel[i].is_thruster) {
				// When in htl mode render with htl method unless its a jump node
				if (is_outlines_only_htl || (!Cmdline_nohtl && !is_outlines_only)) {
					transparent_submodel ts;
					ts.is_submodel = false;
					ts.pop_matrix = false;
					transparent_submodels.push_back(ts);
					model_render_children_buffers( pm, i, Interp_detail_level );
				} else {
					model_interp_subcall( pm, i, Interp_detail_level );
				}
			}
			i = pm->submodel[i].next_sibling;
		}
	}

	// Valathil - now draw the saved transparent objects
	SCP_vector<transparent_submodel>::iterator ts;
	SCP_vector<transparent_object>::iterator obj;

	for(ts = transparent_submodels.begin(); ts != transparent_submodels.end(); ++ts)
	{
		if(ts->is_submodel)
			g3_start_instance_matrix(&ts->model->offset, &ts->orient, true);

		for(obj = ts->transparent_objects.begin(); obj != ts->transparent_objects.end(); ++obj)
		{
			GLOWMAP = obj->glow_map;
			SPECMAP = obj->spec_map;
			NORMMAP = obj->norm_map;
			HEIGHTMAP = obj->height_map;
			MISCMAP = obj->misc_map;

			gr_push_scale_matrix(&obj->scale);
			gr_set_bitmap(obj->texture, obj->blend_filter, GR_BITBLT_MODE_NORMAL, obj->alpha);

			int zbuff = gr_zbuffer_set(GR_ZBUFF_READ);
		
			gr_render_buffer(0, obj->buffer, obj->i, obj->tmap_flags);
		
			gr_zbuffer_set(zbuff);
			gr_pop_scale_matrix();

			GLOWMAP = -1;
			SPECMAP = -1;
			NORMMAP = -1;
			HEIGHTMAP = -1;
			MISCMAP = -1;
		}
		ts->transparent_objects.clear();
		
		if(ts->pop_matrix)
			g3_done_instance(true);
	}
	transparent_submodels.clear();

	if ( !Interp_no_flush ) {
		gr_flush_data_states();

		if (is_outlines_only_htl || (!Cmdline_nohtl && !is_outlines_only)) {
			gr_set_buffer(-1);
		}
	}

	if (is_outlines_only_htl) {
		gr_set_fill_mode(GR_FILL_MODE_SOLID);
	}

	if ( !(Interp_flags & MR_NO_LIGHTING) ) {
		gr_reset_lighting();
		gr_set_lighting(false, false);
	}

	if (Interp_flags & MR_SHOW_PIVOTS )	{
		model_draw_debug_points( pm, NULL );
		model_draw_debug_points( pm, &pm->submodel[pm->detail[Interp_detail_level]] );

		if(pm->flags & PM_FLAG_AUTOCEN){
			gr_set_color(255, 255, 255);
			g3_draw_sphere_ez(&pm->autocenter, pm->rad / 4.5f);
		}
	}

	model_radius = 0.0f;

	// render model insignias
	gr_zbias(1);

	if (!Cmdline_nohtl)	gr_set_texture_panning(0.0, 0.0, false);

	gr_zbuffer_set(GR_ZBUFF_READ);
	model_render_insignias(pm, Interp_detail_level);	

	gr_zbias(0);  

	if (FULLCLOAK != -1)	model_finish_cloak(FULLCLOAK);

	if ( pm->submodel[pm->detail[0]].num_arcs )	{
		interp_render_lightning( pm, &pm->submodel[pm->detail[0]]);
	}	

	if ( Interp_flags & MR_SHOW_SHIELDS )	{
		model_render_shields(pm);
	}	

	if ( Interp_flags & MR_SHOW_PATHS ){
		if (Cmdline_nohtl) model_draw_paths(model_num);
		else model_draw_paths_htl(model_num);
	}

	if (Interp_flags & MR_BAY_PATHS ){
		if (Cmdline_nohtl) model_draw_bay_paths(model_num);
		else model_draw_bay_paths_htl(model_num);
	}

	if ( (Interp_flags & MR_AUTOCENTER) && (set_autocen) ) {
		g3_done_instance(use_api);
	}

	g3_done_instance(use_api);

	// start rendering glow points -Bobboau
	if ( (pm->n_glow_point_banks) && !is_outlines_only && !is_outlines_only_htl && !Glowpoint_override ) {
		model_render_glow_points(pm, shipp, orient, pos, Glowpoint_use_depth_buffer);
	}

	// Draw the thruster glow
	if ( !is_outlines_only && !is_outlines_only_htl ) {
		if ( ( Interp_flags & MR_AUTOCENTER ) && set_autocen ) {
			vec3d autoback_rotated;

			vm_vec_unrotate(&autoback_rotated, &auto_back, orient);
			vm_vec_add2(&autoback_rotated, pos);

			model_render_thrusters( pm, objnum, shipp, orient, &autoback_rotated );
		} else {
			model_render_thrusters( pm, objnum, shipp, orient, pos );
		}
	}

	gr_zbuffer_set(save_gr_zbuffering_mode);

	gr_set_cull(cull);
}


void submodel_render(int model_num, int submodel_num, matrix *orient, vec3d * pos, uint flags, int objnum, int *replacement_textures)
{
	// replacement textures - Goober5000
	model_set_replacement_textures(replacement_textures);

	polymodel * pm;

	MONITOR_INC( NumModelsRend, 1 );	

	if (!(Game_detail_flags & DETAIL_FLAG_MODELS) )	return;

	// Turn off engine effect
	Interp_thrust_scale_subobj=0;

	if (!Model_texturing)
		flags |= MR_NO_TEXTURING;

	Interp_flags = flags;
	Interp_pos=pos;
	
	pm = model_get(model_num);

	// Set the flags we will pass to the tmapper
	Interp_tmap_flags = TMAP_FLAG_GOURAUD | TMAP_FLAG_RGB;

	// if we're in nebula mode
	if((The_mission.flags & MISSION_FLAG_FULLNEB) && (Neb2_render_mode != NEB2_RENDER_NONE)){
		Interp_tmap_flags |= TMAP_FLAG_PIXEL_FOG;
	}

	if ( !(Interp_flags & MR_NO_TEXTURING) )	{
		Interp_tmap_flags |= TMAP_FLAG_TEXTURED;

		if ( (pm->flags & PM_FLAG_ALLOW_TILING) && tiling )
			Interp_tmap_flags |= TMAP_FLAG_TILED;

		if ( !(Interp_flags & MR_NO_CORRECT) )	{
			Interp_tmap_flags |= TMAP_FLAG_CORRECT;
		}
	}

	if ( Interp_flags & MR_ANIMATED_SHADER )
		Interp_tmap_flags |= TMAP_ANIMATED_SHADER;

	bool is_outlines_only_htl = !Cmdline_nohtl && (flags & MR_NO_POLYS) && (flags & MR_SHOW_OUTLINE_HTL);

	//set to true since D3d and OGL need the api matrices set
	g3_start_instance_matrix(pos, orient, true);
	bool set_autocen = false;
	vec3d auto_back = ZERO_VECTOR;
	if (Interp_flags & MR_AUTOCENTER) {
		// standard autocenter using data in model
		if (pm->flags & PM_FLAG_AUTOCEN) {
			auto_back = pm->autocenter;
			vm_vec_scale(&auto_back, -1.0f);
			set_autocen = true;
		}
		// fake autocenter if we are a missile and don't already have autocen info
		else if (Interp_flags & MR_IS_MISSILE) {
            auto_back.xyz.x = -( (pm->submodel[pm->detail[Interp_detail_level]].max.xyz.x + pm->submodel[pm->detail[Interp_detail_level]].min.xyz.x) / 2.0f );
            auto_back.xyz.y = -( (pm->submodel[pm->detail[Interp_detail_level]].max.xyz.y + pm->submodel[pm->detail[Interp_detail_level]].min.xyz.y) / 2.0f );
			auto_back.xyz.z = -( (pm->submodel[pm->detail[Interp_detail_level]].max.xyz.z + pm->submodel[pm->detail[Interp_detail_level]].min.xyz.z) / 2.0f );
			set_autocen = true;
		}

		if (set_autocen)
			g3_start_instance_matrix(&auto_back, NULL, true);
	}

	if (is_outlines_only_htl) {
		gr_set_fill_mode( GR_FILL_MODE_WIRE );

		// lines shouldn't be rendered with textures or special RGB colors (assuming preset colors)
		Interp_flags |= MR_NO_TEXTURING;
		Interp_tmap_flags &= ~TMAP_FLAG_TEXTURED;
		Interp_tmap_flags &= ~TMAP_FLAG_RGB;
		// don't render with lighting either
		Interp_flags |= MR_NO_LIGHTING;
	} else {
		gr_set_fill_mode( GR_FILL_MODE_SOLID );
	}

	if ( !(Interp_flags & MR_NO_LIGHTING ) ) {
		Interp_light = 1.0f;

		light_filter_push( -1, pos, pm->submodel[submodel_num].rad );

		light_rotate_all();

		if (!Cmdline_nohtl) {
			light_set_all_relevent();
		}

		gr_set_lighting(true, true);
	}

	Interp_base_frametime = 0;

	if (objnum >= 0) {
		object *objp = &Objects[objnum];

		if (objp->type == OBJ_SHIP) {
			Interp_base_frametime = Ships[objp->instance].base_texture_anim_frametime;
		}
	}

	// fixes disappearing HUD in OGL - taylor
	int cull = gr_set_cull(1);

	if (!Cmdline_nohtl) {

		// RT - Put this here to fog debris
		if(Interp_tmap_flags & TMAP_FLAG_PIXEL_FOG)
		{
			float fog_near, fog_far;
			object *obj = NULL;
			
			if (objnum >= 0)
				obj = &Objects[objnum];

			neb2_get_adjusted_fog_values(&fog_near, &fog_far, obj);
			unsigned char r, g, b;
			neb2_get_fog_color(&r, &g, &b);
			gr_fog_set(GR_FOGMODE_FOG, r, g, b, fog_near, fog_far);
		}

		gr_set_buffer(pm->vertex_buffer_id);

		transparent_submodel ts;
		ts.is_submodel = false;
		ts.pop_matrix = false;
		transparent_submodels.push_back(ts);

		model_render_buffers(pm, submodel_num);

		SCP_vector<transparent_submodel>::iterator ts_i = transparent_submodels.begin();
		SCP_vector<transparent_object>::iterator obj;
			
		for(obj = ts_i->transparent_objects.begin(); obj != ts_i->transparent_objects.end(); ++obj)
		{
			GLOWMAP = obj->glow_map;
			SPECMAP = obj->spec_map;
			NORMMAP = obj->norm_map;
			HEIGHTMAP = obj->height_map;
			MISCMAP = obj->misc_map;

			gr_push_scale_matrix(&obj->scale);
			gr_set_bitmap(obj->texture, obj->blend_filter, GR_BITBLT_MODE_NORMAL, obj->alpha);

			int zbuff = gr_zbuffer_set(GR_ZBUFF_READ);
		
			gr_render_buffer(0, obj->buffer, obj->i, obj->tmap_flags);
		
			gr_zbuffer_set(zbuff);
			gr_pop_scale_matrix();

			GLOWMAP = -1;
			SPECMAP = -1;
			NORMMAP = -1;
			HEIGHTMAP = -1;
			MISCMAP = -1;
		}
		ts_i->transparent_objects.clear();
		transparent_submodels.clear();

		gr_set_buffer(-1);
	} else {
		model_interp_sub( pm->submodel[submodel_num].bsp_data, pm, &pm->submodel[submodel_num], 0 );
	}

	if ( !(Interp_flags & MR_NO_LIGHTING) ) {
		gr_set_lighting(false, false);
		gr_reset_lighting();
	}

	gr_set_cull(cull);

	gr_set_fill_mode(GR_FILL_MODE_SOLID);

	if ( pm->submodel[submodel_num].num_arcs )	{
		interp_render_lightning( pm, &pm->submodel[submodel_num]);
	}

	if (Interp_flags & MR_SHOW_PIVOTS )
		model_draw_debug_points( pm, &pm->submodel[submodel_num] );

	if ( !(Interp_flags & MR_NO_LIGHTING ) )	{
		light_filter_pop();	
	}
	if (set_autocen)
		g3_done_instance(true);
	g3_done_instance(true);

	// turn off fog after each model renders, RT This fixes HUD being fogged when debris is in target box
	if(The_mission.flags & MISSION_FLAG_FULLNEB){
		gr_fog_set(GR_FOGMODE_NONE, 0, 0, 0);
	}

	gr_flush_data_states();
}

// Fills in an array with points from a model.
// Only gets up to max_num verts;
// Returns number of verts found;
static int submodel_get_points_internal(int model_num, int submodel_num)
{
	polymodel * pm;

	pm = model_get(model_num);

	if ( submodel_num < 0 )	{
		submodel_num = pm->detail[0];
	}

	ubyte *p = pm->submodel[submodel_num].bsp_data;
	int chunk_type, chunk_size;

	chunk_type = w(p);
	chunk_size = w(p+4);

	while (chunk_type != OP_EOF)	{
		switch (chunk_type) {
		case OP_EOF: return 1;
		case OP_DEFPOINTS:	{
				int n;
				int nverts = w(p+8);				
				int offset = w(p+16);
				int nnorms = 0;			

				ubyte * normcount = p+20;
				vec3d *src = vp(p+offset);

				for (n = 0; n < nverts; n++) {
					nnorms += normcount[n];
				}

				model_allocate_interp_data(nverts, nnorms);

				// this must happen only after the interp_data allocation call (since the address changes)
				vec3d **verts = Interp_verts;
				vec3d **norms = Interp_norms;

				for (n=0; n<nverts; n++ )	{
					*verts++ = src;
					*norms++ = src + 1;		// first normal associated with the point

					src += normcount[n]+1;
				} 
				return nverts;		// Read in 'n' points
			}
			break;
		case OP_FLATPOLY:		break;
		case OP_TMAPPOLY:		break;
		case OP_SORTNORM:		break;
		case OP_BOUNDBOX:		break;
		default:
			mprintf(( "Bad chunk type %d, len=%d in submodel_get_points\n", chunk_type, chunk_size ));
			Int3();		// Bad chunk type!
			return 0;
		}
		p += chunk_size;
		chunk_type = w(p);
		chunk_size = w(p+4);
	}
	return 0;		// Couldn't find 'em
}

/**
 * Gets two random points on a model
 */
void submodel_get_two_random_points(int model_num, int submodel_num, vec3d *v1, vec3d *v2, vec3d *n1, vec3d *n2 )
{
	int nv = submodel_get_points_internal(model_num, submodel_num);

	// this is not only because of the immediate div-0 error but also because of the less immediate expectation for at least one point (preferably two) to be found
	if (nv <= 0) {
		polymodel *pm = model_get(model_num);
		Error(LOCATION, "Model %d ('%s') must have at least one point from submodel_get_points_internal!", model_num, (pm == NULL) ? "<null model?!?>" : pm->filename);

		// in case people ignore the error...
		vm_vec_zero(v1);
		vm_vec_zero(v2);
		if (n1 != NULL) {
			vm_vec_zero(n1);
		}
		if (n2 != NULL) {
			vm_vec_zero(n2);
		}
		return;
	}

	int vn1 = (myrand()>>5) % nv;
	int vn2 = (myrand()>>5) % nv;

	*v1 = *Interp_verts[vn1];
	*v2 = *Interp_verts[vn2];

	if(n1 != NULL){
		*n1 = *Interp_norms[vn1];
	}
	if(n2 != NULL){
		*n2 = *Interp_norms[vn2];
	}
}

void submodel_get_two_random_points_better(int model_num, int submodel_num, vec3d *v1, vec3d *v2)
{
	polymodel *pm = model_get(model_num);

	if (pm != NULL) {
		if ( submodel_num < 0 )	{
			submodel_num = pm->detail[0];
		}

		bsp_collision_tree *tree = model_get_bsp_collision_tree(pm->submodel[submodel_num].collision_tree_index);

		int nv = tree->n_verts;

		// this is not only because of the immediate div-0 error but also because of the less immediate expectation for at least one point (preferably two) to be found
		if (nv <= 0) {
			Error(LOCATION, "Model %d ('%s') must have at least one point from submodel_get_points_internal!", model_num, (pm == NULL) ? "<null model?!?>" : pm->filename);

			// in case people ignore the error...
			vm_vec_zero(v1);
			vm_vec_zero(v2);

			return;
		}

		Assert(nv > 0);	// Goober5000 - to avoid div-0 error
		int vn1 = (myrand()>>5) % nv;
		int vn2 = (myrand()>>5) % nv;

		*v1 = tree->point_list[vn1];
		*v2 = tree->point_list[vn2];
	}
}

// If MR_FLAG_OUTLINE bit set this color will be used for outlines.
// This defaults to black.
void model_set_outline_color(int r, int g, int b )
{
	gr_init_color( &Interp_outline_color, r, g, b );

}

// If MR_FLAG_OUTLINE bit set this color will be used for outlines.
// This defaults to black.
void model_set_outline_color_fast(void *outline_color)
{
	Interp_outline_color = *((color*)(outline_color));
}

// IF MR_LOCK_DETAIL is set, then it will always draw detail level 'n'
// This defaults to 0. (0=highest, larger=lower)
void model_set_detail_level(int n)
{
	Interp_detail_level_locked = n;
}

/**
 * Returns number of verts in a submodel;
 */
int submodel_get_num_verts(int model_num, int submodel_num )
{
	polymodel * pm;

	pm = model_get(model_num);

	ubyte *p = pm->submodel[submodel_num].bsp_data;
	int chunk_type, chunk_size;

	chunk_type = w(p);
	chunk_size = w(p+4);

	while (chunk_type != OP_EOF)	{
		switch (chunk_type) {
		case OP_EOF: return 0;
		case OP_DEFPOINTS:	{
				int n=w(p+8);
				return n;		// Read in 'n' points
			}
			break;
		case OP_FLATPOLY:		break;
		case OP_TMAPPOLY:		break;
		case OP_SORTNORM:		break;
		case OP_BOUNDBOX:	break;
		default:
			mprintf(( "Bad chunk type %d, len=%d in submodel_get_num_verts\n", chunk_type, chunk_size ));
			Int3();		// Bad chunk type!
			return 0;
		}
		p += chunk_size;
		chunk_type = w(p);
		chunk_size = w(p+4);
	}
	return 0;		// Couldn't find 'em
}

/**
 * Returns number of tmaps & flat polys in a submodel;
 */
int submodel_get_num_polys_sub( ubyte *p )
{
	int chunk_type = w(p);
	int chunk_size = w(p+4);
	int n = 0;
	
	while (chunk_type != OP_EOF)	{
		switch (chunk_type) {
		case OP_EOF:			return n;
		case OP_DEFPOINTS:	break;
		case OP_FLATPOLY:		n++; break;
		case OP_TMAPPOLY:		n++; break;
		case OP_SORTNORM:		{
			int frontlist = w(p+36);
			int backlist = w(p+40);
			int prelist = w(p+44);
			int postlist = w(p+48);
			int onlist = w(p+52);
			n += submodel_get_num_polys_sub(p+frontlist);
			n += submodel_get_num_polys_sub(p+backlist);
			n += submodel_get_num_polys_sub(p+prelist);
			n += submodel_get_num_polys_sub(p+postlist );
			n += submodel_get_num_polys_sub(p+onlist );
			}
			break;
		case OP_BOUNDBOX:	break;
		default:
			mprintf(( "Bad chunk type %d, len=%d in submodel_get_num_polys\n", chunk_type, chunk_size ));
			Int3();		// Bad chunk type!
			return 0;
		}
		p += chunk_size;
		chunk_type = w(p);
		chunk_size = w(p+4);
	}
	return n;		
}

/**
 * Returns number of tmaps & flat polys in a submodel
 */
int submodel_get_num_polys(int model_num, int submodel_num )
{
	polymodel * pm;

	pm = model_get(model_num);

	return submodel_get_num_polys_sub( pm->submodel[submodel_num].bsp_data );
}

// Sets the submodel instance data in a submodel
// If show_damaged is true it shows only damaged submodels.
// If it is false it shows only undamaged submodels.
void model_show_damaged(int model_num, int show_damaged )
{
	polymodel * pm;
	int i;

	pm = model_get(model_num);

	for (i=0; i<pm->n_models; i++ )	{
		bsp_info *sm = &pm->submodel[i];

		// Set the "blown out" flags	
		sm->blown_off = 0;
	}

	for (i=0; i<pm->n_models; i++ )	{
		bsp_info *sm = &pm->submodel[i];

		// Set the "blown out" flags	
		if ( show_damaged )	{
			if ( sm->my_replacement > -1 )	{
				pm->submodel[sm->my_replacement].blown_off = 0;
				sm->blown_off = 1;
			}
		} else {
			if ( sm->my_replacement > -1 )	{
				pm->submodel[sm->my_replacement].blown_off = 1;
				sm->blown_off = 0;
			}
		}
	}
}

/**
 * Set the insignia bitmap to be used when rendering a ship with an insignia (-1 switches it off altogether)
 */
void model_set_insignia_bitmap(int bmap)
{
	Interp_insignia_bitmap = bmap;
}

void model_set_replacement_textures(int *replacement_textures)
{
	Interp_new_replacement_textures = replacement_textures;
}

/**
 * Set the forces bitmap
 */
void model_set_forced_texture(int bmap)
{
	Interp_forced_bitmap = bmap;
}

/**
 * Set model transparency for use with MR_ALL_XPARENT
 */
void model_set_alpha(float alpha)
{
	Interp_xparent_alpha = alpha;
}

/**
 * See if the given texture is used by the passed model. 0 if not used, 1 if used, -1 on error
 */
int model_find_texture(int model_num, int bitmap)
{
	polymodel * pm;	
	int idx;

	// get a handle to the model
	pm = model_get(model_num);
	if(pm == NULL){
		return -1;
	}

	// find the texture
	for(idx=0; idx<pm->n_textures; idx++)
	{
		if(pm->maps[idx].FindTexture(bitmap) > -1)
		{
			return 1;
		}
	}

	// no texture
	return 0;
}

// find closest point on extended bounding box (the bounding box plus all the planes that make it up)
// returns closest distance to extended box
// positive return value means start_point is outside extended box
// displaces closest point an optional amount delta to the outside of the box
// closest_box_point can be NULL.
float get_model_closest_box_point_with_delta(vec3d *closest_box_point, vec3d *start_point, int modelnum, int *is_inside, float delta)
{
	int i, idx;
	vec3d box_point, ray_direction, *extremes;
	float dist, best_dist;
	polymodel *pm;
	int inside = 0;
	int masks[6] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20};
	int mask_inside = 0x3f;

	best_dist = FLT_MAX;
	pm = model_get(modelnum);

	for (i=0; i<6; i++) {
		idx = i / 2;	// which row vector of Identity matrix

		memcpy(&ray_direction, vmd_identity_matrix.a2d[idx], sizeof(vec3d));

		// do negative, then positive plane for each axis
		if (2 * idx == i) {
			extremes = &pm->mins;
			vm_vec_negate(&ray_direction);
		} else {
			extremes = &pm->maxs;
		}

		// a negative distance means started outside the box
		dist = fvi_ray_plane(&box_point, extremes, &ray_direction, start_point, &ray_direction, 0.0f);
		if (dist > 0) {
			inside |= masks[i];
		}
		if (fabs(dist) < fabs(best_dist)) {
			best_dist = dist;
			if (closest_box_point) {
				vm_vec_scale_add(closest_box_point, &box_point, &ray_direction, delta);
			}
		}
	}

	// is start_point inside the box
	if (is_inside) {
		*is_inside = (inside == mask_inside);
	}

	return -best_dist;
}

// find closest point on extended bounding box (the bounding box plus all the planes that make it up)
// returns closest distance to extended box
// positive return value means start_point is outside extended box
// displaces closest point an optional amount delta to the outside of the box
// closest_box_point can be NULL.
float get_world_closest_box_point_with_delta(vec3d *closest_box_point, object *box_obj, vec3d *start_point, int *is_inside, float delta)
{
	vec3d temp, box_start;
	float dist;
	int modelnum;

	// get modelnum
	modelnum = Ship_info[Ships[box_obj->instance].ship_info_index].model_num;

	// rotate start_point to box_obj RF
	vm_vec_sub(&temp, start_point, &box_obj->pos);
	vm_vec_rotate(&box_start, &temp, &box_obj->orient);

	dist = get_model_closest_box_point_with_delta(closest_box_point, &box_start, modelnum, is_inside, delta);

	// rotate closest_box_point to world RF
	if (closest_box_point) {
		vm_vec_unrotate(&temp, closest_box_point, &box_obj->orient);
		vm_vec_add(closest_box_point, &temp, &box_obj->pos);
	}

	return dist;
}

void model_set_fog_level(float l)
{
	Interp_fog_level = l;
}

/**
 * Given a newly loaded model, page in all textures
 */
void model_page_in_textures(int modelnum, int ship_info_index)
{
	int i, idx;
	polymodel *pm = model_get(modelnum);

	// bogus
	if (pm == NULL)
		return;

	for (idx = 0; idx < pm->n_textures; idx++) {
		pm->maps[idx].PageIn();
	}

	for (i = 0; i < pm->n_glow_point_banks; i++) {
		glow_point_bank *bank = &pm->glow_point_banks[i];

		bm_page_in_texture(bank->glow_bitmap);
		bm_page_in_texture(bank->glow_neb_bitmap);
	}

	if (ship_info_index >= 0)
		ship_page_in_textures(ship_info_index);
}

// unload all textures for a given model
// "release" should only be set if called from model_unload()!!!
void model_page_out_textures(int model_num, bool release)
{
	int i, j;

	if (model_num < 0)
		return;

	polymodel *pm = model_get(model_num);

	if (pm == NULL)
		return;

	if (release && (pm->used_this_mission > 0))
		return;


	for (i = 0; i < pm->n_textures; i++) {
		pm->maps[i].PageOut(release);
	}

	// NOTE: "release" doesn't work here for some, as of yet unknown, reason - taylor
	for (j = 0; j < pm->n_glow_point_banks; j++) {
		glow_point_bank *bank = &pm->glow_point_banks[j];

		if (bank->glow_bitmap >= 0) {
		//	if (release) {
		//		bm_release(bank->glow_bitmap);
		//		bank->glow_bitmap = -1;
		//	} else {
				bm_unload(bank->glow_bitmap);
		//	}
		}

		if (bank->glow_neb_bitmap >= 0) {
		//	if (release) {
		//		bm_release(bank->glow_neb_bitmap);
		//		bank->glow_neb_bitmap = -1;
		//	} else {
				bm_unload(bank->glow_neb_bitmap);
		//	}
		}
	}
}


//**********vertex buffer stuff**********//
int tri_count[MAX_MODEL_TEXTURES];
poly_list polygon_list[MAX_MODEL_TEXTURES];

void parse_defpoint(int off, ubyte *bsp_data)
{
	int i, n;
	int nverts = w(off+bsp_data+8);	
	int offset = w(off+bsp_data+16);
	int next_norm = 0;

	ubyte *normcount = off+bsp_data+20;
	vec3d *src = vp(off+bsp_data+offset);

	// Get pointer to lights
	Interp_lights = off+bsp_data+20+nverts;

#ifndef NDEBUG
	modelstats_num_verts += nverts;
#endif

	for (n = 0; n < nverts; n++) {
		Interp_verts[n] = src;
		src++; // move to normal

		for (i = 0; i < normcount[n]; i++) {
			Interp_norms[next_norm] = src;

			next_norm++;
			src++;
		}
	}
}

int check_values(vec3d *N)
{
	// Values equal to -1.#IND0
	if(!is_valid_vec(N))
	{
		N->xyz.x = 1.0f;
		N->xyz.y = 0.0f;
		N->xyz.z = 0.0f;
		return 1;
	}

	return 0;
}

int Parse_normal_problem_count = 0;

void parse_tmap(int offset, ubyte *bsp_data)
{
	int pof_tex = w(bsp_data+offset+40);
	int n_vert = w(bsp_data+offset+36);
	
	ubyte *p = &bsp_data[offset+8];
	model_tmap_vert *tverts;

	tverts = (model_tmap_vert *)&bsp_data[offset+44];

	vertex *V;
	vec3d *v;
	vec3d *N;

	int problem_count = 0;

	for (int i = 1; i < (n_vert-1); i++) {
		V = &polygon_list[pof_tex].vert[(polygon_list[pof_tex].n_verts)];
		N = &polygon_list[pof_tex].norm[(polygon_list[pof_tex].n_verts)];
		v = Interp_verts[(int)tverts[0].vertnum];
		V->world.xyz.x = v->xyz.x;
		V->world.xyz.y = v->xyz.y;
		V->world.xyz.z = v->xyz.z;
		V->texture_position.u = tverts[0].u;
		V->texture_position.v = tverts[0].v;

		*N = *Interp_norms[(int)tverts[0].normnum];

		if ( IS_VEC_NULL(N) )
			*N = *vp(p);

	  	problem_count += check_values(N);
		vm_vec_normalize_safe(N);

		V = &polygon_list[pof_tex].vert[(polygon_list[pof_tex].n_verts)+1];
		N = &polygon_list[pof_tex].norm[(polygon_list[pof_tex].n_verts)+1];
		v = Interp_verts[(int)tverts[i].vertnum];
		V->world.xyz.x = v->xyz.x;
		V->world.xyz.y = v->xyz.y;
		V->world.xyz.z = v->xyz.z;
		V->texture_position.u = tverts[i].u;
		V->texture_position.v = tverts[i].v;

		*N = *Interp_norms[(int)tverts[i].normnum];

		if ( IS_VEC_NULL(N) )
			*N = *vp(p);

	 	problem_count += check_values(N);
		vm_vec_normalize_safe(N);

		V = &polygon_list[pof_tex].vert[(polygon_list[pof_tex].n_verts)+2];
		N = &polygon_list[pof_tex].norm[(polygon_list[pof_tex].n_verts)+2];
		v = Interp_verts[(int)tverts[i+1].vertnum];
		V->world.xyz.x = v->xyz.x;
		V->world.xyz.y = v->xyz.y;
		V->world.xyz.z = v->xyz.z;
		V->texture_position.u = tverts[i+1].u;
		V->texture_position.v = tverts[i+1].v;

		*N = *Interp_norms[(int)tverts[i+1].normnum];

		if ( IS_VEC_NULL(N) )
			*N = *vp(p);

		problem_count += check_values(N);
		vm_vec_normalize_safe(N);

		polygon_list[pof_tex].n_verts += 3;
	}

	Parse_normal_problem_count += problem_count;
}

void parse_sortnorm(int offset, ubyte *bsp_data);

void parse_bsp(int offset, ubyte *bsp_data)
{
	int id = w(bsp_data+offset);
	int size = w(bsp_data+offset+4);

	while (id != 0) {
		switch (id)
		{
			case OP_EOF:	
				return;

			case OP_DEFPOINTS:
				parse_defpoint(offset, bsp_data);
				break;

			case OP_SORTNORM:
				parse_sortnorm(offset, bsp_data);
				break;

			case OP_FLATPOLY:
				break;

			case OP_TMAPPOLY:
				parse_tmap(offset, bsp_data);
				break;

			case OP_BOUNDBOX:
				break;

			default:
				return;
		}

		offset += size;
		id = w(bsp_data+offset);
		size = w(bsp_data+offset+4);

		if (size < 1)
			id = OP_EOF;
	}
}

void parse_sortnorm(int offset, ubyte *bsp_data)
{
	int frontlist, backlist, prelist, postlist, onlist;

	frontlist = w(bsp_data+offset+36);
	backlist = w(bsp_data+offset+40);
	prelist = w(bsp_data+offset+44);
	postlist = w(bsp_data+offset+48);
	onlist = w(bsp_data+offset+52);

	if (prelist) parse_bsp(offset+prelist,bsp_data);
	if (backlist) parse_bsp(offset+backlist, bsp_data);
	if (onlist) parse_bsp(offset+onlist, bsp_data);
	if (frontlist) parse_bsp(offset+frontlist, bsp_data);
	if (postlist) parse_bsp(offset+postlist, bsp_data);
}

void find_tmap(int offset, ubyte *bsp_data)
{
	int pof_tex = w(bsp_data+offset+40);
	int n_vert = w(bsp_data+offset+36);

	tri_count[pof_tex] += n_vert-2;	
}

void find_defpoint(int off, ubyte *bsp_data)
{
	int n;
	int nverts = w(off+bsp_data+8);	

	ubyte * normcount = off+bsp_data+20;

	// Get pointer to lights
	Interp_lights = off+bsp_data+20+nverts;

#ifndef NDEBUG
	modelstats_num_verts += nverts;
#endif

	int norm_num = 0;

	for (n = 0; n < nverts; n++) {	
		norm_num += normcount[n];
	}

	Interp_num_verts = nverts;
	Interp_num_norms = norm_num;
}

void find_sortnorm(int offset, ubyte *bsp_data);

// tri_count
void find_tri_counts(int offset, ubyte *bsp_data)
{
	int id = w(bsp_data+offset);
	int size = w(bsp_data+offset+4);

	while (id != 0) {
		switch (id)
		{
			case OP_EOF:	
				return;

			case OP_DEFPOINTS:
				find_defpoint(offset, bsp_data);
				break;

			case OP_SORTNORM:
				find_sortnorm(offset, bsp_data);
				break;

			case OP_FLATPOLY:
				break;

			case OP_TMAPPOLY:
				find_tmap(offset, bsp_data);
				break;

			case OP_BOUNDBOX:
				break;

			default:
				return;
		}

		offset += size;
		id = w(bsp_data+offset);
		size = w(bsp_data+offset+4);

		if (size < 1)
			id = OP_EOF;
	}
}

void find_sortnorm(int offset, ubyte *bsp_data)
{
	int frontlist, backlist, prelist, postlist, onlist;

	frontlist = w(bsp_data+offset+36);
	backlist = w(bsp_data+offset+40);
	prelist = w(bsp_data+offset+44);
	postlist = w(bsp_data+offset+48);
	onlist = w(bsp_data+offset+52);

	if (prelist) find_tri_counts(offset+prelist,bsp_data);
	if (backlist) find_tri_counts(offset+backlist, bsp_data);
	if (onlist) find_tri_counts(offset+onlist, bsp_data);
	if (frontlist) find_tri_counts(offset+frontlist, bsp_data);
	if (postlist) find_tri_counts(offset+postlist, bsp_data);
}


static void allocate_poly_list()
{
	for (int i = 0; i < MAX_MODEL_TEXTURES; i++) {
		polygon_list[i].allocate(tri_count[i]*3);
	}

	model_allocate_interp_data(Interp_num_verts, Interp_num_norms);
}

void interp_pack_vertex_buffers(polymodel *pm, int mn)
{
	Assert( pm->vertex_buffer_id >= 0 );
	Assert( (mn >= 0) && (mn < pm->n_models) );

	bsp_info *model = &pm->submodel[mn];

	if ( !model->buffer.model_list ) {
		return;
	}

	bool rval = gr_pack_buffer(pm->vertex_buffer_id, &model->buffer);

	if ( !rval ) {
		Error( LOCATION, "Unable to pack vertex buffer for '%s'\n", pm->filename );
	}
}

void interp_configure_vertex_buffers(polymodel *pm, int mn)
{
	int i, j, first_index;
	uint total_verts = 0;
	SCP_vector<int> vertex_list;

	Assert( (mn >= 0) && (mn < pm->n_models) );

	bsp_info *model = &pm->submodel[mn];

	for (i = 0; i < MAX_MODEL_TEXTURES; i++) {
		polygon_list[i].n_verts = 0;
		tri_count[i] = 0;
	}

	find_tri_counts(0, model->bsp_data);


	for (i = 0; i < MAX_MODEL_TEXTURES; i++) {
		total_verts += tri_count[i];

		// for the moment we can only support INT_MAX worth of verts per index buffer
		if (tri_count[i] > INT_MAX) {
		    Error( LOCATION, "Unable to generate vertex buffer data because model '%s' with %i verts is over the maximum of %i verts!\n", pm->filename, tri_count[i], INT_MAX);
		}
	}

	if (total_verts < 1) {
		return;
	}

	allocate_poly_list();

	parse_bsp(0, model->bsp_data);

	total_verts = 0;

	for (i = 0; i < MAX_MODEL_TEXTURES; i++) {
		total_verts += polygon_list[i].n_verts;
	}

	poly_list *model_list = new(std::nothrow) poly_list;

	if ( !model_list ) {
		Error( LOCATION, "Unable to allocate memory for poly_list!\n" );
	}

	model->buffer.model_list = model_list;

	model_list->allocate( (int)total_verts );

	for (i = 0; i < MAX_MODEL_TEXTURES; i++) {
		if ( !polygon_list[i].n_verts )
			continue;

		memcpy( (model_list->vert) + model_list->n_verts, polygon_list[i].vert, sizeof(vertex) * polygon_list[i].n_verts );
		memcpy( (model_list->norm) + model_list->n_verts, polygon_list[i].norm, sizeof(vec3d) * polygon_list[i].n_verts );

		if (Cmdline_normal) {
			memcpy( (model_list->tsb) + model_list->n_verts, polygon_list[i].tsb, sizeof(tsb_t) * polygon_list[i].n_verts );
		}

		model_list->n_verts += polygon_list[i].n_verts;
	}

	// IBX stuff
	extern IBX ibuffer_info;

	if (ibuffer_info.read != NULL) {
		int ibx_verts = 0;
		int ibx_size = 0;

		ibx_verts = cfread_int( ibuffer_info.read );
		ibuffer_info.size -= sizeof(int);	// subtract

		// vertex count (indexed vertex count)
		ibx_size += ibx_verts * sizeof(int);
		// index count (original vertex count)
		ibx_size += model_list->n_verts * sizeof(int);

		// safety check for this section
		// ibuffer_info.size should be greater than or equal to ibx_size at this point
		if (ibx_size > ibuffer_info.size) {
			// AAAAAHH! not enough stored data - Abort, Retry, Fail?
			Warning(LOCATION, "IBX: Safety Check Failure!  The file doesn't contain enough data, deleting '%s'\n", ibuffer_info.name);

			cfclose( ibuffer_info.read );
			ibuffer_info.read = NULL;
			ibuffer_info.size = 0;
			cf_delete( ibuffer_info.name, CF_TYPE_CACHE );

			// force generate
			model_list->make_index_buffer(vertex_list);

			vertex_list.clear();	// don't actually need this now
		} else {
			poly_list *tlist = new(std::nothrow) poly_list;

			if ( !tlist ) {
				Error( LOCATION, "Unable to allocate memory for IBX poly_list!\n" );
			}

			tlist->allocate( ibx_verts );

			// we have to generate tangent data manually for model_list
			// (since it's otherwise done during make_index_buffer())
			model_list->calculate_tangent();

			for (i = 0; i < ibx_verts; i++) {
				int ivert = cfread_int( ibuffer_info.read );

				tlist->vert[i] = model_list->vert[ivert];
				tlist->norm[i] = model_list->norm[ivert];

				if (Cmdline_normal) {
					tlist->tsb[i] = model_list->tsb[ivert];
				}
			}

			tlist->n_verts = ibx_verts;

			// change from old model_list to new one
			delete model_list;

			model->buffer.model_list = tlist;
			model_list = tlist;

			// subtract this block of data from the total size for next check
			// remember that this includes the next set of reads too
			ibuffer_info.size -= ibx_size;
		}
	} else {
		// no read file so we'll have to generate
		model_list->make_index_buffer(vertex_list);

		if (ibuffer_info.write != NULL) {
			cfwrite_int( model_list->n_verts, ibuffer_info.write );

			int count = (int)vertex_list.size();
			Assert( model_list->n_verts == count );

			for (i = 0; i < count; i++) {
				cfwrite_int( vertex_list[i], ibuffer_info.write );
			}
		}

		vertex_list.clear();	// done
	}
	// end IBX stuff

	int vertex_flags = (VB_FLAG_POSITION | VB_FLAG_NORMAL | VB_FLAG_UV1);

	if (model_list->tsb != NULL) {
		Assert( Cmdline_normal );
		vertex_flags |= VB_FLAG_TANGENT;
	}

	model->buffer.flags = vertex_flags;

	for (i = 0; i < MAX_MODEL_TEXTURES; i++) {
		if ( !polygon_list[i].n_verts )
			continue;

		buffer_data new_buffer(polygon_list[i].n_verts);

		Verify( new_buffer.get_index() != NULL );

		for (j = 0; j < polygon_list[i].n_verts; j++) {
			if (ibuffer_info.read != NULL) {
				first_index = cfread_int(ibuffer_info.read);
				Assert( first_index >= 0 );

				new_buffer.assign(j, first_index);
			} else {
				first_index = model_list->find_index(&polygon_list[i], j);
				Assert(first_index != -1);

				new_buffer.assign(j, first_index);

				if (ibuffer_info.write != NULL) {
					cfwrite_int(first_index, ibuffer_info.write);
				}
			}
		}

		new_buffer.texture = i;

		new_buffer.flags = 0;

		if (polygon_list[i].n_verts >= USHRT_MAX) {
			new_buffer.flags |= VB_FLAG_LARGE_INDEX;
		}

		model->buffer.tex_buf.push_back( new_buffer );
	}

	bool rval = gr_config_buffer(pm->vertex_buffer_id, &model->buffer);

	if ( !rval ) {
		Error( LOCATION, "Unable to configure vertex buffer for '%s'\n", pm->filename );
	}
}


inline int in_box(vec3d *min, vec3d *max, vec3d *pos)
{
	vec3d point;

	vm_vec_sub(&point, &View_position, pos);

	if ( (point.xyz.x >= min->xyz.x) && (point.xyz.x <= max->xyz.x)
		&& (point.xyz.y >= min->xyz.y) && (point.xyz.y <= max->xyz.y)
		&& (point.xyz.z >= min->xyz.z) && (point.xyz.z <= max->xyz.z) )
	{
		return 1;
	}

	return -1;
}

inline int in_sphere(vec3d *pos, float radius)
{
	if ( vm_vec_dist(&View_position, pos) <= radius )
		return 1;
	else
		return -1;
}


void model_render_children_buffers(polymodel *pm, int mn, int detail_level)
{
	int i;

	if ( (mn < 0) || (mn >= pm->n_models) ) {
		Int3();
		return;
	}

	bsp_info *model = &pm->submodel[mn];
	
	if (model->blown_off)
		return;

	uint fl = Interp_flags;

	if (model->is_thruster) {
		if ( !(Interp_flags & MR_SHOW_THRUSTERS) )
			return;

		Interp_flags |= MR_NO_LIGHTING;
		Interp_thrust_scale_subobj = 1;
		gr_set_lighting(false, false);
	} else {
		Interp_thrust_scale_subobj = 0;
	}

	// if using detail boxes or spheres, check that we are valid for the range
	if ( !(Interp_flags & MR_FULL_DETAIL) && model->use_render_box ) {
		vm_vec_copy_scale(&Interp_render_box_min, &model->render_box_min, Interp_box_scale);
		vm_vec_copy_scale(&Interp_render_box_max, &model->render_box_max, Interp_box_scale);

		if ( (-model->use_render_box + in_box(&Interp_render_box_min, &Interp_render_box_max, &model->offset)) )
			return;
	}
	if ( !(Interp_flags & MR_FULL_DETAIL) && model->use_render_sphere ) {
		Interp_render_sphere_radius = model->render_sphere_radius * Interp_box_scale;

		// TODO: doesn't consider submodel rotations yet -zookeeper
		vec3d offset;
		model_find_submodel_offset(&offset, pm->id, mn);
		vm_vec_add2(&offset, &model->render_sphere_offset);

		if ( (-model->use_render_sphere + in_sphere(&offset, Interp_render_sphere_radius)) )
			return;
	}

	// Get submodel rotation data and use submodel orientation matrix
	// to put together a matrix describing the final orientation of
	// the submodel relative to its parent
	angles ang = model->angs;

	// Add barrel rotation if needed
	if (model->gun_rotation) {
		if ( pm->gun_submodel_rotation > PI2 ) {
			pm->gun_submodel_rotation -= PI2;
		} else if ( pm->gun_submodel_rotation < 0.0f ) {
			pm->gun_submodel_rotation += PI2;
		}

		ang.b += pm->gun_submodel_rotation;
	}

	// Compute final submodel orientation by using the orientation matrix
	// and the rotation angles.
	// By using this kind of computation, the rotational angles can always
	// be computed relative to the submodel itself, instead of relative
	// to the parent
	matrix rotation_matrix = model->orientation;
	vm_rotate_matrix_by_angles(&rotation_matrix, &ang);

	matrix inv_orientation;
	vm_copy_transpose_matrix(&inv_orientation, &model->orientation);

	matrix submodel_matrix;
	vm_matrix_x_matrix(&submodel_matrix, &rotation_matrix, &inv_orientation);

	g3_start_instance_matrix(&model->offset, &submodel_matrix, true);
	
	transparent_submodel ts;
	ts.is_submodel = true;
	ts.pop_matrix = true;

	bool childs = false;
	i = model->first_child;
	while (i >= 0) {
		if ( !pm->submodel[i].is_thruster ) {
			childs = true;
			ts.pop_matrix = false;
			break;
		}

		i = pm->submodel[i].next_sibling;
	}
	ts.model = model;
	
	memcpy(&ts.orient,&submodel_matrix,sizeof(matrix));
	transparent_submodels.push_back(ts);
	model_render_buffers(pm, mn, true);

	if (Interp_flags & MR_SHOW_PIVOTS)
		model_draw_debug_points( pm, &pm->submodel[mn] );

	if (model->num_arcs)
		interp_render_lightning( pm, &pm->submodel[mn]);

	i = model->first_child;

	while (i >= 0) {
		if ( !pm->submodel[i].is_thruster ) {
			model_render_children_buffers( pm, i, detail_level );
		}

		i = pm->submodel[i].next_sibling;
	}

	if(childs)
	{
		ts.is_submodel = false;
		ts.pop_matrix = true;
		ts.model = 0;
		ts.transparent_objects.clear();
		transparent_submodels.push_back(ts);
	}

	Interp_flags = fl;

	if (Interp_thrust_scale_subobj) {
		Interp_thrust_scale_subobj = 0;

		if ( !(Interp_flags & MR_NO_LIGHTING) ) {
			gr_set_lighting(true, true);
		}
	}

	g3_done_instance(true);
}

void model_render_buffers(polymodel *pm, int mn, bool is_child)
{
	if (pm->vertex_buffer_id < 0)
		return;

	if ( (mn < 0) || (mn >= pm->n_models) ) {
		Int3();
		return;
	}

	bsp_info *model = &pm->submodel[mn];

	// if using detail boxes or spheres, check that we are valid for the range
	if ( !is_child && !(Interp_flags & MR_FULL_DETAIL) && model->use_render_box ) {
		vm_vec_copy_scale(&Interp_render_box_min, &model->render_box_min, Interp_box_scale);
		vm_vec_copy_scale(&Interp_render_box_max, &model->render_box_max, Interp_box_scale);

		if ( (-model->use_render_box + in_box(&Interp_render_box_min, &Interp_render_box_max, &model->offset)) )
			return;
	}
	if ( !is_child && !(Interp_flags & MR_FULL_DETAIL) && model->use_render_sphere ) {
		Interp_render_sphere_radius = model->render_sphere_radius * Interp_box_scale;

		// TODO: doesn't consider submodel rotations yet -zookeeper
		vec3d offset;
		model_find_submodel_offset(&offset, pm->id, mn);
		vm_vec_add2(&offset, &model->render_sphere_offset);

		if ( (-model->use_render_sphere + in_sphere(&offset, Interp_render_sphere_radius)) )
			return;
	}

	vec3d scale;

	if (Interp_thrust_scale_subobj) {
		scale.xyz.x = 1.0f;
		scale.xyz.y = 1.0f;
		scale.xyz.z = Interp_thrust_scale;
	} else {
		scale.xyz.x = Interp_warp_scale_x;
		scale.xyz.y = Interp_warp_scale_y;
		scale.xyz.z = Interp_warp_scale_z;
	}

	texture_info tex_replace[TM_NUM_TYPES];

	int no_texturing = (Interp_flags & MR_NO_TEXTURING);

	int forced_texture = -2;
	float forced_alpha = 1.0f;
	int forced_blend_filter = GR_ALPHABLEND_NONE;

	if ( (Interp_flags & MR_FORCE_TEXTURE) && (Interp_forced_bitmap >= 0) ) {
		forced_texture = Interp_forced_bitmap;
	} else if (Interp_warp_bitmap >= 0) {
		forced_texture = Interp_warp_bitmap;
		forced_alpha = Interp_warp_alpha;
		forced_blend_filter = GR_ALPHABLEND_FILTER;
	} else if (Interp_thrust_scale_subobj) {
		if ( (Interp_thrust_bitmap >= 0) && (Interp_thrust_scale > 0.0f) ) {
			forced_texture = Interp_thrust_bitmap;
		} else {
			forced_texture = -1;
		}

		forced_alpha = 1.2f;
		forced_blend_filter = GR_ALPHABLEND_FILTER;
	} else if (Interp_flags & MR_ALL_XPARENT) {
		forced_alpha = Interp_xparent_alpha;
		forced_blend_filter = GR_ALPHABLEND_FILTER;
	}

	if (!Interp_thrust_scale_subobj) {
		gr_push_scale_matrix(&scale);
	}

	size_t buffer_size = model->buffer.tex_buf.size();

	for (size_t i = 0; i < buffer_size; i++) {
		int texture = -1;
		int tmap_num = model->buffer.tex_buf[i].texture;
		texture_map *tmap = &pm->maps[tmap_num];
		int rt_begin_index = tmap_num*TM_NUM_TYPES;
		float alpha = 1.0f;
		int blend_filter = GR_ALPHABLEND_NONE;

		if (forced_texture != -2) {
			texture = forced_texture;
			alpha = forced_alpha;
		}
		else if ( !no_texturing ) {
			// pick the texture, animating it if necessary
			if ( (Interp_new_replacement_textures != NULL) && (Interp_new_replacement_textures[rt_begin_index + TM_BASE_TYPE] == REPLACE_WITH_INVISIBLE) ) {
				// invisible textures aren't rendered, but we still have to skip assigning the underlying model texture
				texture = -1;
			} else if ( (Interp_new_replacement_textures != NULL) && (Interp_new_replacement_textures[rt_begin_index + TM_BASE_TYPE] >= 0) ) {
				// an underlying texture is replaced with a real new texture
				tex_replace[TM_BASE_TYPE] = texture_info(Interp_new_replacement_textures[rt_begin_index + TM_BASE_TYPE]);
				texture = model_interp_get_texture(&tex_replace[TM_BASE_TYPE], Interp_base_frametime);
			} else {
				// we just use the underlying texture
				texture = model_interp_get_texture(&tmap->textures[TM_BASE_TYPE], Interp_base_frametime);
			}

			if (texture < 0) {
				continue;
			}

			// doing glow maps?
			if ( !(Interp_flags & MR_NO_GLOWMAPS) ) {
				texture_info *tglow = &tmap->textures[TM_GLOW_TYPE];

				if ( (Interp_new_replacement_textures != NULL) && (Interp_new_replacement_textures[rt_begin_index + TM_GLOW_TYPE] >= 0) ) {
					tex_replace[TM_GLOW_TYPE] = texture_info(Interp_new_replacement_textures[rt_begin_index + TM_GLOW_TYPE]);
					GLOWMAP = model_interp_get_texture(&tex_replace[TM_GLOW_TYPE], Interp_base_frametime);
				} else if (tglow->GetTexture() >= 0) {
					// shockwaves are special, their current frame has to come out of the shockwave code to get the timing correct
					if ( (Interp_objnum >= 0) && (Objects[Interp_objnum].type == OBJ_SHOCKWAVE) && (tglow->GetNumFrames() > 1) ) {
						GLOWMAP = tglow->GetTexture() + shockwave_get_framenum(Objects[Interp_objnum].instance, tglow->GetNumFrames());
					} else {
						GLOWMAP = model_interp_get_texture(tglow, Interp_base_frametime);
					}
				}
			}

			if ( (Detail.lighting > 2)  && (Interp_detail_level < 2) ) {
				// likewise, etc.
				texture_info *spec_map = &tmap->textures[TM_SPECULAR_TYPE];
				texture_info *norm_map = &tmap->textures[TM_NORMAL_TYPE];
				texture_info *height_map = &tmap->textures[TM_HEIGHT_TYPE];
				texture_info *misc_map = &tmap->textures[TM_MISC_TYPE];

				if (Interp_new_replacement_textures != NULL) {
					if (Interp_new_replacement_textures[rt_begin_index + TM_SPECULAR_TYPE] >= 0) {
						tex_replace[TM_SPECULAR_TYPE] = texture_info(Interp_new_replacement_textures[rt_begin_index + TM_SPECULAR_TYPE]);
						spec_map = &tex_replace[TM_SPECULAR_TYPE];
					}

					if (Interp_new_replacement_textures[rt_begin_index + TM_NORMAL_TYPE] >= 0) {
						tex_replace[TM_NORMAL_TYPE] = texture_info(Interp_new_replacement_textures[rt_begin_index + TM_NORMAL_TYPE]);
						norm_map = &tex_replace[TM_NORMAL_TYPE];
					}

					if (Interp_new_replacement_textures[rt_begin_index + TM_HEIGHT_TYPE] >= 0) {
						tex_replace[TM_HEIGHT_TYPE] = texture_info(Interp_new_replacement_textures[rt_begin_index + TM_HEIGHT_TYPE]);
						height_map = &tex_replace[TM_HEIGHT_TYPE];
					}

					if (Interp_new_replacement_textures[rt_begin_index + TM_MISC_TYPE] >= 0) {
						tex_replace[TM_MISC_TYPE] = texture_info(Interp_new_replacement_textures[rt_begin_index + TM_MISC_TYPE]);
						misc_map = &tex_replace[TM_MISC_TYPE];
					}
				}

				SPECMAP = model_interp_get_texture(spec_map, Interp_base_frametime);
				NORMMAP = model_interp_get_texture(norm_map, Interp_base_frametime);
				HEIGHTMAP = model_interp_get_texture(height_map, Interp_base_frametime);
				MISCMAP = model_interp_get_texture(misc_map, Interp_base_frametime);
			}
		} else {
			alpha = forced_alpha;
		}

		if ( (texture == -1) && !no_texturing ) {
			continue;
		}

		// trying to get transperent textures-Bobboau
		if (tmap->is_transparent) {
			// for special shockwave/warpmap usage
			alpha = (Interp_warp_alpha != -1.0f) ? Interp_warp_alpha : 0.8f;
			blend_filter = GR_ALPHABLEND_FILTER;

			//Valathil - Save the object for later rendering
			transparent_object tobj;
			tobj.alpha=alpha;
			tobj.blend_filter = blend_filter;
			tobj.buffer = &model->buffer;
			tobj.glow_map = GLOWMAP;
			tobj.height_map = HEIGHTMAP;
			tobj.misc_map = MISCMAP;
			tobj.i = i;
			tobj.norm_map = NORMMAP;
			tobj.spec_map = SPECMAP;
			tobj.texture = texture;
			tobj.tmap_flags = Interp_tmap_flags;
			memcpy(&tobj.scale,&scale,sizeof(vec3d));
			transparent_submodels.back().transparent_objects.push_back(tobj);
		}
		else
		{
			if (forced_blend_filter != GR_ALPHABLEND_NONE) {
				blend_filter = forced_blend_filter;
			}

			gr_set_bitmap(texture, blend_filter, GR_BITBLT_MODE_NORMAL, alpha);

			gr_render_buffer(0, &model->buffer, i, Interp_tmap_flags);
		}

		GLOWMAP = -1;
		SPECMAP = -1;
		NORMMAP = -1;
		HEIGHTMAP = -1;
		MISCMAP = -1;
	}

	gr_pop_scale_matrix();
}

// returns 1 if the thruster should be drawn
//         0 if it shouldn't
int model_should_render_engine_glow(int objnum, int bank_obj)
{
	if ((bank_obj <= -1) || (objnum <= -1))
		return 1;

	object *obj = &Objects[objnum];

	if (obj == NULL)
		return 1;

	if (obj->type == OBJ_SHIP) {
		ship_subsys *ssp;
		ship *shipp = &Ships[obj->instance];
		ship_info *sip = &Ship_info[shipp->ship_info_index];

		Assert( bank_obj < sip->n_subsystems );

		char subname[MAX_NAME_LEN];
		// shipp->subsystems isn't always valid here so don't use it
		strncpy(subname, sip->subsystems[bank_obj].subobj_name, MAX_NAME_LEN);

		ssp = GET_FIRST(&shipp->subsys_list);
		while ( ssp != END_OF_LIST( &shipp->subsys_list ) ) {
			if ( !strcmp(subname, ssp->system_info->subobj_name) ) {
				// this subsystem has 0 or less hits, ie. it's destroyed
				if ( ssp->current_hits <= 0 )
					return 0;

				// see if the subsystem is disrupted, in which case it should be inoperable
				if ( ship_subsys_disrupted(ssp) )
					return 0;

				return 1;
			}
			ssp = GET_NEXT( ssp );
		}

	} else if (obj->type == OBJ_WEAPON) {
		// for weapons, if needed in the future
	}

	// default to render glow
	return 1;
}

// Goober5000
// uses same algorithms as in ship_do_thruster_frame
int model_interp_get_texture(texture_info *tinfo, fix base_frametime)
{
	int texture, frame, num_frames;
	float cur_time, total_time;

	// get texture
	num_frames = tinfo->GetNumFrames();
	texture = tinfo->GetTexture();
	total_time = tinfo->GetTotalTime();

	// maybe animate it
	if (texture >= 0 && num_frames > 1)
	{
		// sanity check total_time first thing
		Assert(total_time > 0.0f);

		cur_time = f2fl((game_get_overall_frametime() - base_frametime) % fl2f(total_time));

		// get animation frame
		frame = fl2i((cur_time * num_frames) / total_time);
		if (frame < 0) frame = 0;
		if (frame >= num_frames) frame = num_frames - 1;

		// advance to the correct frame
		texture += frame;
	}

	// done
	return texture;
}

void model_set_warp_globals(float scale_x, float scale_y, float scale_z, int bitmap_id, float alpha)
{
	Interp_warp_scale_x = scale_x;
	Interp_warp_scale_y = scale_y;
	Interp_warp_scale_z = scale_z;

	Interp_warp_bitmap = bitmap_id;
	Interp_warp_alpha = alpha;
}

//********************-----CLASS: texture_info-----********************//
texture_info::texture_info()
{
	clear();
}
texture_info::texture_info(int bm_handle)
{
	if(!bm_is_valid(bm_handle))
	{
		clear();
		return;
	}

	this->original_texture = bm_handle;
	this->ResetTexture();
}
void texture_info::clear()
{
	texture = original_texture = -1;
	num_frames = 0;
	total_time = 1.0f;
}
int texture_info::GetNumFrames()
{
	return num_frames;
}
int texture_info::GetOriginalTexture()
{
	return original_texture;
}
int texture_info::GetTexture()
{
	return texture;
}
float texture_info::GetTotalTime()
{
	return total_time;
}
int texture_info::LoadTexture(char *filename, char *dbg_name = "<UNKNOWN>")
{
	if (strlen(filename) + 4 >= NAME_LENGTH) //Filenames are passed in without extension
	{
		mprintf(("Generated texture name %s is too long. Skipping...\n"));
		return -1;
	}
	this->original_texture = bm_load_either(filename, NULL, NULL, NULL, 1, CF_TYPE_MAPS);
	if(this->original_texture < 0)
		nprintf(("Maps", "For \"%s\" I couldn't find %s.ani\n", dbg_name, filename));
	this->ResetTexture();

	return texture;
}
void texture_info::PageIn()
{
	bm_page_in_texture(texture);
}

void texture_info::PageOut(bool release)
{
	if (texture >= 0) {
		if (release) {
			bm_release(texture);
			texture = -1;
			num_frames = 0;
			total_time = 1.0f;
		} else {
			bm_unload(texture);
		}
	}
}
int texture_info::ResetTexture()
{
	return this->SetTexture(original_texture);
}
int texture_info::SetTexture(int n_tex)
{
	if(n_tex != -1 && !bm_is_valid(n_tex))
		return texture;

	//Set the new texture
	texture = n_tex;

	//If it is intentionally invalid, blank everything else
	if(n_tex == -1)
	{
		num_frames = 0;
		total_time = 1.0f;
	}
	else
	{
		//Determine the num_frames and total_time values.
		int fps = 0;
		this->num_frames = 1;

		bm_get_info(texture, NULL, NULL, NULL, &this->num_frames, &fps);

		this->total_time = (num_frames / ((fps > 0) ? (float)fps : 1.0f));
	}

	return texture;
}

//********************-----CLASS: texture_map-----********************//
int texture_map::FindTexture(int bm_handle)
{
	if(!bm_is_valid(bm_handle))
		return -1;

	for(int i = 0; i < TM_NUM_TYPES; i++)
	{
		if (this->textures[i].GetTexture() == bm_handle)
			return i;
	}
	return -1;
}
int texture_map::FindTexture(char *fname)
{
	if(fname == NULL || !strlen(fname))
		return -1;

	char buf[NAME_LENGTH];
	for(int i = 0; i < TM_NUM_TYPES; i++)
	{
		bm_get_filename(this->textures[i].GetTexture(), buf);
		if (!strextcmp(buf, fname)) {
			return i;
		}
	}
	return -1;
}

void texture_map::PageIn()
{
	for(int i = 0; i < TM_NUM_TYPES; i++)
		this->textures[i].PageIn();
}

void texture_map::PageOut(bool release)
{
	for(int i = 0; i < TM_NUM_TYPES; i++)
		this->textures[i].PageOut(release);
}

void texture_map::Reset()
{
	for(int i = 0; i < TM_NUM_TYPES; i++)
		this->textures[i].ResetTexture();
}
