
#ifndef __skelmodel_h__
#include "skelmodel.h"
#endif

#ifndef PandaWave_RESOURCES
#include "RevengeResources.h"
#endif

#ifndef PandaWave_XMESSAGES
#include <XMessages.h>
#endif

#ifndef PandaWave_XLIST
#include <XList.h>
#endif

#ifndef PandaWave_XWINDOW
#include <XWindow.h>
#endif

#ifndef PandaWave_XSLIDECONTROL
#include <XSlideControl.h>
#endif

#ifndef PandaWave_XPOPUP
#include <XPopup.h>
#endif

#ifndef __tags_h__
#include "tags.h"
#endif

#ifndef __read_skl_h__
#include "read_skl.h"
#endif

#ifndef __fvec_c1_h__
#include "fvec_c1.h"
#endif

#ifndef PandaWave_XFILE
#include <XFile.h>
#endif

#ifndef __TskmdViewer_h__
#include "TskmdViewer.h"
#endif

#ifndef PandaWave_XDIALOGS
#include <XDialog.h>
#endif

#include <XError.h>
#include "memalign.h"
#include "tagpick.h"
#include "RevengeApp.h"
#include "formatters.h"

#include <ctype.h>
#include "vec3.h"

#if 1

#define assert(A) ((void)0)

#else
#define enter_debugger() __asm {int 3}
#define assert(expr) if (!(expr)) { enter_debugger() }

#endif


byte_swap_code bs_skmd_tag_header[] =
{
	_begin_bs_array, 1,
	
	// tag animation
	_4byte,
	
	// unsigned animation_checksum
	_4byte,
	
	// word modelcount
	_2byte,
	
	// word flags
	_2byte,
	
	_begin_bs_array, MAX_SKMD_MODELS,
		
		// unsigned offset
		_4byte,
		
		// unsigned length
		_4byte,
		
		// word flags
		_2byte,
		
		// word probability
		_2byte,
		
		// model pointer
		4,
		
	_end_bs_array,
	
	_end_bs_array
};


byte_swap_code bs_skmd_modelheader[] =
{
	_begin_bs_array, 1,
	
	MAX_SKMD_MODELNAME+1,	//	char name[MAX_SKMD_MODELNAME+1];
	_4byte,	//	tag texture_stack;
	_2byte, //	word hard_min_vertices;
	_2byte, //	word user_min_vertices;
	_2byte, //	word max_vertices;
	_2byte, //	word lod_count;
	_2byte, //	word bone_count;
	_2byte, //	word bone_fixed_shift;
	_2byte, //	word anim_offset_scale;
	_2byte, //	word anim_offset_shift;
	_4byte, //	float model_scale;
	_4byte, //	world_distance collision_radius;
	_4byte, //	world_distance collision_height0;
	_4byte, //	world_distance collision_height1;
	_4byte, //	world_distance pick_radius;
	_4byte, //	world_distance pick_height0;
	_4byte, //	world_distance pick_height1;
	_4byte, //	world_distance hilight_radius;
	_4byte, //	world_distance hilight_height0;
	_4byte, //	world_distance hilight_height1;
	_2byte, //	word complex_collision_tri_count;
	_2byte, //	word complex_collision_box_count;
	
	62, //	byte padding[62];
	
	2, // short loaded_texturestacks_index;
	
	4, //	float (*uv)[2];	// internal pointer used when loaded
	4, //	struct skmd_complex_collision_data *complex_collision;	// internal pointer used when loaded
	
	//	skmd_lodref lods[MAX_SKMD_PREMADELOD];
	_begin_bs_array, MAX_SKMD_PREMADELOD,		// skmd_lodref lods[MAX_SKMD_PREMADELOD];
		_2byte,		// word min_vertices;
		_2byte,		// word max_vertices;
		_4byte,		// unsigned offset;	// from skmd_modelheader start
		_4byte,		// unsigned length;
		4,			// const struct skmd_lod *data;	// used when loaded
	_end_bs_array,
	
	// then the bones
	// align-to-16
	// then the uv
	// then the collision
	_end_bs_array
};

byte_swap_code bs_skmd_bone[] = 
{
	_begin_bs_array, 1,
	
	MAX_SKMD_BONENAME+1,	//	char name[MAX_SKMD_BONENAME+1];
	_4byte,	//	unsigned flags;
		
	_2byte,	//	signed short parent;	// can be -1 == no parent
	2,		//	signed short anim_bone; // can be -1 == none; run-time
	
	_4byte, _4byte, _4byte, _4byte,	//	float float_orient[4];
	_4byte, _4byte, _4byte,	//	float float_offset[3];
		
	_2byte, _2byte, _2byte, _2byte, //	signed short fixed_orient[4];
	_2byte, _2byte, _2byte,	//	signed short fixed_offset[3];
	
	_2byte,	//	signed short attachment_index; // run-time
	_4byte,	//	tag attachment_id;
	
	_begin_bs_array, 3*2,	// float bounds[3][2]
		_4byte,
	_end_bs_array,
	
	_4byte, //	float shadow_near;
	_4byte, //	float shadow_far;
	_4byte, //	float shadow_width;
		
	_4byte, //	tag custom_shadow_tag;
	_byte,	//	byte custom_shadow_stack;
	_byte,	//	byte custom_shadow_level;
	2,		//	short shadow_texture_entry_index;
		
	_4byte, //	float tree_mass;
	_4byte,	//	float tree_resistance;
	_4byte,	//	float tree_dampening;
	
	_end_bs_array
};

byte_swap_code bs_skmd_complex_collision_tri[] =
{
	_begin_bs_array, 1,
	
	_2byte, _2byte, _2byte,
	_2byte, _2byte, _2byte,
	_2byte, _2byte, _2byte,
	_2byte, _2byte, _2byte,
	
	_4byte,
	_4byte,
	_4byte,
	_4byte,
	
	_end_bs_array
};

byte_swap_code bs_skmd_complex_collision_box[] =
{
	_begin_bs_array, 1,
	
	//	short_world_rectangle3d bounds;
 	_2byte, _2byte, _2byte, _2byte, _2byte, _2byte,
 	
	//	short sub_box_first_index, sub_box_count;
	_2byte, _2byte,
	
	//	short triangle_first_index, triangle_count;
	_2byte, _2byte,
	
	_end_bs_array
};

#define ALIGN16(p) ((((size_t)(p)) + 15) & ~15)


inline void MatMult( float out[3][3], const float a[3][3], const float b[3][3] )
{
	float temp[3][3];
	for (int i = 0; i < 3; ++i) {
		for (int j = 0; j < 3; ++j) {
			temp[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j] + a[i][2] * b[2][j];
		}
	}
	memcpy( out, temp, sizeof(temp) );
}


unsigned setup_skmd_modelheader_ptrs( skmd_modelheader *mh )
{
	assert( mh == (skmd_modelheader *)ALIGN16(mh) );
	for (int i = 0; i < mh->lod_count; ++i) {
		mh->lods[i].data = (skmd_lod *)((byte *)mh + mh->lods[i].offset);
	}
	mh->uv = (float (*)[2])(ALIGN16( &mh->bones[mh->bone_count] ));
	mh->complex_collision_boxes = (struct skmd_complex_collision_box *)(mh->uv + mh->max_vertices);
	mh->complex_collision_tris = (struct skmd_complex_collision_tri *)(mh->complex_collision_boxes + mh->complex_collision_box_count);
	mh->real_vertex_counts = (word *)(mh->complex_collision_tris + mh->complex_collision_tri_count) - mh->hard_min_vertices;
	return ((size_t)(mh->real_vertex_counts + mh->max_vertices+1) - (size_t)mh);
}


unsigned import_skmd_modelheader( skmd_modelheader *mh )	// returns length
{
	assert( mh == (skmd_modelheader *)ALIGN16(mh) );
	
	byte_swap_data_be( "skmd_modelheader", mh, sizeof(mh) - sizeof(skmd_bone), 1, bs_skmd_modelheader );
	byte_swap_data_be( "skmd_bone", mh->bones, sizeof(skmd_bone), mh->bone_count, bs_skmd_bone );
	unsigned len = setup_skmd_modelheader_ptrs(mh);
	byte_swap_memory_be( "uv coords", (void *)mh->uv, mh->max_vertices * 2, _4byte );
	byte_swap_data_be( "complex collision boxes", mh->complex_collision_boxes, sizeof(skmd_complex_collision_box), mh->complex_collision_box_count, bs_skmd_complex_collision_box );
	byte_swap_data_be( "complex collision tris", mh->complex_collision_tris, sizeof(skmd_complex_collision_tri), mh->complex_collision_tri_count, bs_skmd_complex_collision_tri );
	byte_swap_memory_be( "real vertex count", mh->real_vertex_counts + mh->hard_min_vertices, mh->max_vertices+1 - mh->hard_min_vertices, _2byte );
	return len;
}


unsigned export_skmd_modelheader( skmd_modelheader *mh )
{
	int i, j;
	
	assert( mh == (skmd_modelheader *)ALIGN16(mh) );
	setup_skmd_modelheader_ptrs( mh );
	unsigned size = ((size_t)(mh->real_vertex_counts + mh->max_vertices+1 - mh->hard_min_vertices)) - ((size_t)mh);
	
	byte_swap_memory_be( "uv coords", (void *)mh->uv, mh->max_vertices * 2, _4byte );
	mh->uv = 0;
	
	byte_swap_data_be( "complex collision boxes", mh->complex_collision_boxes, sizeof(skmd_complex_collision_box), mh->complex_collision_box_count, bs_skmd_complex_collision_box );
	mh->complex_collision_boxes = 0;
	
	byte_swap_data_be( "complex collision tris", mh->complex_collision_tris, sizeof(skmd_complex_collision_tri), mh->complex_collision_tri_count, bs_skmd_complex_collision_tri );
	mh->complex_collision_tris = 0;
	
	byte_swap_memory_be( "real vertex count", mh->real_vertex_counts + mh->hard_min_vertices, mh->max_vertices+1 - mh->hard_min_vertices, _2byte );
	mh->real_vertex_counts = 0;
	
	for (i = 0; i < mh->lod_count; ++i) {
		mh->lods[i].data = 0;
	}
	
	float biggest = 0.00001f;
	for (i = 0; i < mh->bone_count; ++i)
	{
		for (j = 0; j < 3; ++j)
		{
			float offset = fabs(mh->bones[i].float_offset[j] * mh->model_scale);
			if (offset > biggest) biggest = offset;
		}
	}
	mh->bone_fixed_shift = 0;
	float scale = 1.0f;
	while (biggest * scale < 16383.0)
	{
		scale *= 2.0f;
		mh->bone_fixed_shift += 1;
	}
	for (i = 0; i < mh->bone_count; ++i)
	{
		for (j = 0; j < 3; ++j)
		{
			mh->bones[i].fixed_offset[j] = float_to_clamped_short( mh->model_scale * mh->bones[i].float_offset[j] * scale );
		}
	}
	
	scale = mh->model_scale;
	mh->anim_offset_shift = 0;
	while (fabs(scale) < 16384.0)
	{
		scale *= 2.0f;
		mh->anim_offset_shift += 1;
	}
	mh->anim_offset_scale = float_to_clamped_short( scale );
	
	byte_swap_data_be( "skmd_bone", mh->bones, sizeof(skmd_bone), mh->bone_count, bs_skmd_bone );
	byte_swap_data_be( "skmd_modelheader", mh, sizeof(mh) - sizeof(skmd_bone), 1, bs_skmd_modelheader );
	
	return size;
}


unsigned setup_skmd_lod_ptrs( int bone_count, int min_vertex, int max_vertex, skmd_lod *lod )
{
	assert( lod == (skmd_lod *)ALIGN16(lod) );
	
	word *weights_per_bone = (word *)(&lod->triangles[lod->max_triangles]);
	word *edge_collapse = (word *)(&weights_per_bone[bone_count]) - min_vertex;
	word *min_vertices_for_tri = (word *)(&edge_collapse[max_vertex]);
	word *weight_refs = (word *)(&min_vertices_for_tri[lod->max_triangles]);
	byte *weights_per_vertex = (byte *)(&weight_refs[lod->weight_ref_count]);
	float (*weights)[4] = (float (*)[4])ALIGN16(&weights_per_vertex[max_vertex]);
	
	lod->weights_per_bone = weights_per_bone;
	lod->edge_collapse = edge_collapse;
	lod->min_vertices_for_tri = min_vertices_for_tri;
	lod->weight_refs = weight_refs;
	lod->weights_per_vertex = weights_per_vertex;
	lod->weights = weights;
	
	return (size_t)(&weights[lod->weighted_vert_count]) - (size_t)lod;
}


unsigned import_skmd_lod( int bone_count, int min_vertex, int max_vertex, skmd_lod *lod )
{
	assert( lod == (skmd_lod *)ALIGN16(lod) );
	
	byte_swap_memory_be( "lod_header", lod, 6, _2byte );
	unsigned size = setup_skmd_lod_ptrs( bone_count, min_vertex, max_vertex, lod );
	
	byte_swap_memory_be( "weights_per_bone", (void *)lod->weights_per_bone, bone_count, _2byte );
	byte_swap_memory_be( "edge_collapse", (void *)(lod->edge_collapse + min_vertex), max_vertex - min_vertex, _2byte );
	byte_swap_memory_be( "min_vertices_for_tri", (void *)(lod->min_vertices_for_tri), lod->max_triangles, _2byte );
	byte_swap_memory_be( "weight_refs", (void *)lod->weight_refs, lod->weight_ref_count, _2byte );
	// don't swap single-byte weight_count
	byte_swap_memory_be( "skmd_quadvert", (void *)lod->weights, lod->weighted_vert_count * 4, _4byte );
	byte_swap_memory_be( "triangles", (void *)lod->triangles, lod->max_triangles * 3, _2byte );
	
	return size;
}


unsigned export_skmd_lod( int bone_count, int min_vertex, int max_vertex, skmd_lod *lod )
{
	assert( lod == (skmd_lod *)ALIGN16(lod) );
	setup_skmd_lod_ptrs( bone_count, min_vertex, max_vertex, lod );
	
	unsigned size = (size_t)(lod->weights + lod->weighted_vert_count) - (size_t)lod;
	
	byte_swap_memory_be( "weights_per_bone", (void *)lod->weights_per_bone, bone_count, _2byte );
	byte_swap_memory_be( "edge_collapse", (void *)(lod->edge_collapse + min_vertex), max_vertex - min_vertex, _2byte );
	byte_swap_memory_be( "min_vertices_for_tri", (void *)(lod->min_vertices_for_tri), lod->max_triangles, _2byte );
	byte_swap_memory_be( "weight_refs", (void *)lod->weight_refs, lod->weight_ref_count, _2byte );
	// don't swap single-byte weight_count
	byte_swap_memory_be( "skmd_quadvert", (void *)lod->weights, lod->weighted_vert_count * 4, _4byte );
	byte_swap_memory_be( "triangles", (void *)lod->triangles, lod->max_triangles * 3, _2byte );
	
	lod->weights_per_bone = 0;
	lod->edge_collapse = 0;
	lod->min_vertices_for_tri = 0;
	lod->weight_refs = 0;
	lod->weights_per_vertex = 0;
	lod->weights = 0;
	
	byte_swap_memory_be( "lod_header", lod, 6, _2byte );
	
	return size;
}


bool bounds_to_shadow( skmd_bone &b, const float bounds[3][2] )
{
	b.flags &= ~_skmd_bone_shadow_dimension_mask;
	b.flags |= _skmd_bone_shadow_dimension_none << _skmd_bone_shadow_dimension_bit_0;
	if (bounds[0][0] >= bounds[0][1]) return false;
	if (bounds[1][0] >= bounds[1][1]) return false;
	if (bounds[2][0] >= bounds[2][1]) return false;
	
	int best_dim = -1;
	float best_rad;
	float low_err = 100000000000.0f;
	for (int j = 0; j < 3; ++j) {
		int d1 = j + 1;
		if (d1 >= 3) d1 -= 3;
		int d2 = d1 + 1;
		if (d2 >= 3) d2 -= 3;
		float rad = 0.25f * (bounds[d1][1] - bounds[d1][0] +
							bounds[d2][1] - bounds[d2][0]);
		float err = (fabs(bounds[d1][1] - rad) +
					fabs(bounds[d1][0] + rad) +
					fabs(bounds[d2][1] - rad) +
					fabs(bounds[d2][0] + rad)) / rad;
		if (err < low_err) {
			low_err = err;
			best_dim = j;
		#if 0
			float test;
			rad = fabs(bounds[d1][1]);
			test = fabs(bounds[d1][0]);
			if (test > rad) rad = test;
			test = fabs(bounds[d2][1]);
			if (test > rad) rad = test;
			test = fabs(bounds[d2][0]);
			if (test > rad) rad = test;
		#endif
			best_rad = rad;
		}
	}
	if (best_dim >= 0) {
		b.flags &= ~_skmd_bone_shadow_dimension_mask;
		b.flags |= best_dim << _skmd_bone_shadow_dimension_bit_0;
		float length = 0.6f * (bounds[best_dim][1] - bounds[best_dim][0]);
		float mid_length = 0.5f * (bounds[best_dim][0] + bounds[best_dim][1]);
		b.shadow_near = mid_length - length;//build_info.bounds[best_dim][0];
		b.shadow_far = mid_length + length;//build_info.bounds[best_dim][1];
		b.shadow_width = best_rad;
	}
	
	return true;
}


void add_default_biped_shadows( skmd_modelheader *mh )
{
	static const char *names[] = {
		"Spine",
		"Neck",
		"UpperArm",
		"Forearm",
		"Thigh",
		"Calf",
		"Foot",
		"Head",
		"Hand",
		0
	};
	
	for (int i = 0; i < mh->bone_count; ++i) {
		struct skmd_bone &b = mh->bones[i];
		int j = 0;
		while (names[j]) {
			if (strstr( b.name, names[j] )) {
				if (bounds_to_shadow( b, b.bounds ))
				{
					b.flags |= _skmd_bone_shadowgen_enabled_flag;
				}
				break;
			}
			++j;
		}
	}
}


skmd_modelheader *create_skmd_from_skl( XGFileSpecifier &fs )
{
	FILE *f = fs.OpenFile("r");
	read_skl( f );
	fix_normals();
	fill_npatch_cracks();
	divide_st();
	compact_mesh();
	compact_bones();
	set_skl_to_frame( 0, false );
	compute_bone_bounds();
	build_collapse_order();
	
	skmd_modelheader *mh = make_skmd_modelheader();
	if (mh) {
		char name[256];
		fs.GetBaseName( name );
		strncpy( mh->name, name, MAX_SKMD_MODELNAME );
		mh->name[MAX_SKMD_MODELNAME] = 0;
		
		add_default_biped_shadows( mh );
		
		int vert_diff = mh->max_vertices - mh->hard_min_vertices;
		int lod_count = 1 + vert_diff / MINIMUM_LOD_INCREMENT;
		if (lod_count > MAX_SKMD_PREMADELOD - 1) lod_count = MAX_SKMD_PREMADELOD - 1;
		
		int low = mh->hard_min_vertices;
		skmd_lodref *ref = mh->lods+0;
		
		for (int i = 0; i < lod_count; ++i)
		{
			int high = mh->hard_min_vertices + (vert_diff * (i + 1)) / lod_count;
			high = mh->real_vertex_counts[high];
			
			ref->min_vertices = low;
			ref->max_vertices = high;
			ref->offset = 0;
			ref->data = make_skmd_lod( low, high, false );
			++ref;
			
			low = high;
		}
		
		// n-patch
		ref->min_vertices = mh->max_vertices;
		ref->max_vertices = mh->max_vertices;
		ref->offset = 0;
		ref->data = make_skmd_lod( mh->max_vertices, mh->max_vertices, true );
		ref->data->flags |= _skmd_lod_has_npatch_fill_flag;
		
		mh->lod_count = lod_count + 1;
	}
	
	fclose( f );
	return mh;
}


#pragma mark -


const skmd_lodref *pick_skmd_lod( const skmd_modelheader *m, int *vertices )
{
	int v = *vertices;
	if (v > m->max_vertices) v = m->max_vertices; else if (v < m->hard_min_vertices) v = m->hard_min_vertices;
	v = m->real_vertex_counts[v];
	const skmd_lodref *bestlod = 0;
	int besterror = 2000000;
	int bestv = v;
	for (int i = 0; i < m->lod_count; ++i) {
		int error;
		if (v > m->lods[i].max_vertices) {
			error = 1000000 + (v - m->lods[i].max_vertices);
			if (error < besterror) {
				bestlod = m->lods + i;
				besterror = error;
				bestv = m->lods[i].max_vertices;
			}
		} else if (v < m->lods[i].min_vertices) {
			error = 1000000 + (m->lods[i].min_vertices - v);
			if (error < besterror) {
				bestlod = m->lods + i;
				besterror = error;
				bestv = m->lods[i].min_vertices;
			}
		} else {
			error = m->lods[i].max_vertices - v;
			if (error < besterror) {
				bestlod = m->lods + i;
				besterror = error;
				bestv = v;
			}
		}
	}
	
	*vertices = bestv;
	return bestlod;
}


void generate_skel_mesh( const object_bone *bones, int bone_count,
						const skmd_lodref *ref, int target_vert_count,
						void *out_vert, void *out_norm,
						unsigned short *out_tris, int *out_tri_count )
{
	int i, j, k;
	static fvec vert_temp[3][8000/FVEC_WIDTH];
	static fvec vert2_temp[8000 << (2 - FVEC_WIDTH_SHIFT)];
	
	const skmd_lod *lod = ref->data;
	
	// transform the offsets and normals and save in SoA format
	int qv_pos = 0;
	for (i = 0; i < bone_count; ++i) {
		int bone_vert = lod->weights_per_bone[i];
		if (!bone_vert) continue;
		int fv_rounded_verts = (bone_vert + FVEC_WIDTH - 1) >> FVEC_WIDTH_SHIFT;
		int qv_rounded_verts = (bone_vert + 3) & ~3;
		for (j = 0; j < 3; ++j) {
			fvec off = fv_loadf(bones[i].world_pos[j]);
			fvec mx = fv_loadf(bones[i].m[j][0]);
			fvec my = fv_loadf(bones[i].m[j][1]);
			fvec mz = fv_loadf(bones[i].m[j][2]);
			fvec *v_out = vert_temp[j] + qv_pos;
			const fvec *in = (const fvec *)(lod->weights[qv_pos]);
			for (k = 0; k < fv_rounded_verts; ++k) {
				fvec vert = fv_mul( in[0], mx );
				vert = fv_muladd( vert, in[1], my );
				vert = fv_muladd( vert, in[2], mz );
				*v_out++ = fv_muladd( vert, in[3], off );
				in += 4;
			}
		}
		qv_pos += qv_rounded_verts;
	}
	
	// put the offsets into AoS format
	for (i = 0; i < (lod->weighted_vert_count >> FVEC_WIDTH_SHIFT); ++i) {
		fv_merge3pad_tomem( (float *)(vert2_temp + (i << 2)), vert_temp[0][i], vert_temp[1][i], vert_temp[2][i] );
	}
	
	// sum the vertex weights
	int w_step = 0;
	fvec *out_pos = (fvec *)(out_vert);
	fvec *out_n = (fvec *)(out_norm);
	for (i = 0; i < target_vert_count; ++i) {
		int w_count = lod->weights_per_vertex[i];
		{
			int v_pos = (lod->weight_refs[w_step]) << (2 - FVEC_WIDTH_SHIFT);
			fvec v0 = vert2_temp[v_pos];
			#if FVEC_WIDTH < 4
			fvec v1 = vert2_temp[v_pos+1];
			#endif
			#if FVEC_WIDTH < 2
			fvec v2 = vert2_temp[v_pos+2];
			#endif
			
			++w_step;
			for (j = 1; j < w_count; ++j) {
				v_pos = (lod->weight_refs[w_step]) << (2 - FVEC_WIDTH_SHIFT);
				v0 = fv_add( v0, vert2_temp[v_pos] );
				#if FVEC_WIDTH < 4
				v1 = fv_add( v1, vert2_temp[v_pos+1] );
				#endif
				#if FVEC_WIDTH < 2
				v2 = fv_add( v2, vert2_temp[v_pos+2] );
				#endif
				
				++w_step;
			}
			out_pos[0] = v0;
			#if FVEC_WIDTH < 4
			out_pos[1] = v1;
			#endif
			#if FVEC_WIDTH < 2
			out_pos[2] = v2;
			#endif
			out_pos += 4 >> FVEC_WIDTH_SHIFT;
		}
		{
			int n_pos = (lod->weight_refs[w_step]) << (2 - FVEC_WIDTH_SHIFT);
			fvec n0 = vert2_temp[n_pos];
			#if FVEC_WIDTH < 4
			fvec n1 = vert2_temp[n_pos+1];
			#endif
			#if FVEC_WIDTH < 2
			fvec n2 = vert2_temp[n_pos+2];
			#endif
			
			++w_step;
			for (j = 1; j < w_count; ++j) {
				n_pos = (lod->weight_refs[w_step]) << (2 - FVEC_WIDTH_SHIFT);
				n0 = fv_add( n0, vert2_temp[n_pos] );
				#if FVEC_WIDTH < 4
				n1 = fv_add( n1, vert2_temp[n_pos+1] );
				#endif
				#if FVEC_WIDTH < 2
				n2 = fv_add( n2, vert2_temp[n_pos+2] );
				#endif
				
				++w_step;
			}
			out_n[0] = n0;
			#if FVEC_WIDTH < 4
			out_n[1] = n1;
			#endif
			#if FVEC_WIDTH < 2
			out_n[2] = n2;
			#endif
			out_n += 4 >> FVEC_WIDTH_SHIFT;
		}
	}
	
	// output triangles
	static unsigned short collapse[4000];
	for (i = 0; i < target_vert_count; ++i) {
		collapse[i] = i;
	}
	for (i = target_vert_count; i < ref->max_vertices; ++i) {
		collapse[i] = collapse[lod->edge_collapse[i]];
	}
	int max_tri_count = lod->max_triangles;
	int tri_count = 0;
	for (i = 0; i < max_tri_count; ++i) {
		if (lod->min_vertices_for_tri[i] > target_vert_count) continue;
		out_tris[0] = collapse[lod->triangles[i][0]];
		out_tris[1] = collapse[lod->triangles[i][1]];
		out_tris[2] = collapse[lod->triangles[i][2]];
		out_tris += 3;
		++tri_count;
	}
	*out_tri_count = tri_count;
}


static float get_tri_vert_list_bounds( const vec3 (*v)[3], int count, float out[3][2] )
{
	int i, j, k;
	if (count < 1)
	{
		for (i = 0; i < 3; ++i) {
			for (j = 0; j < 2; ++j) {
				out[i][j] = 0.0f;
			}
		}
		return - 1.0f;
	}
	
	for (i = 0; i < 3; ++i) {
		for (j = 0; j < 2; ++j) {
			out[i][j] = v[0][0][i];
		}
	}
	
	for (i = 0; i < count; ++i)
	{
		for (j = 0; j < 3; ++j)
		{
			const vec3 &p = v[i][j];
			for (k = 0; k < 3; ++k)
			{
				if (p[k] < out[k][0]) out[k][0] = p[k];
				if (p[k] > out[k][1]) out[k][1] = p[k];
			}
		}
	}
	
	return (out[0][1] - out[0][0]) * (out[1][1] - out[1][0]) * (out[2][1] - out[2][0]);
}


static void split_tri_vert_list( const vec3 (*in)[3], int in_count,
								vec3 (**out_a)[3], int *out_count_a,
								vec3 (**out_b)[3], int *out_count_b,
								vec3 plane_normal, float plane_const )
{
	int i;
	
	vec3 (*a)[3] = new vec3[in_count*2][3];
	int a_count = 0;
	vec3 (*b)[3] = new vec3[in_count*2][3];
	int b_count = 0;
	
	for (i = 0; i < in_count; ++i)
	{
		vec3 pt = (in[i][0] + in[i][1] + in[i][2]) / 3.0f;
		if (dot( plane_normal, pt ) + plane_const < 0.0f)
		{
			a[a_count][0] = in[i][0];
			a[a_count][1] = in[i][1];
			a[a_count][2] = in[i][2];
			++a_count;
		}
		else
		{
			b[b_count][0] = in[i][0];
			b[b_count][1] = in[i][1];
			b[b_count][2] = in[i][2];
			++b_count;
		}
	}
	
	*out_a = a;
	*out_b = b;
	*out_count_a = a_count;
	*out_count_b = b_count;
}

#define MAX_COLLISION_BOXES 4000
#define MAX_COLLISION_TRIS 4000

static int collision_box_count;
static skmd_complex_collision_box collision_boxes[MAX_COLLISION_BOXES];
static int collision_tri_count;
static skmd_complex_collision_tri collision_tris[MAX_COLLISION_TRIS];
#define MINIMUM_COLLISION_BOX_SIZE 0.25f
#define MINIMUM_TRIS_IN_COLLISION_BOX 2

static void out_plane( skmd_complex_collision_tri *tri, int plane, vec3 norm, vec3 pointa, vec3 pointb, vec3 pointc, const vec3 *assert_front )
{
	norm = normalized( norm ) * TRIG_MAGNITUDE;
	tri->normals[plane][0] = float_to_clamped_short( norm.x );
	tri->normals[plane][1] = float_to_clamped_short( norm.y );
	tri->normals[plane][2] = float_to_clamped_short( norm.z );
	double d0 = tri->normals[plane][0] * pointa.x + tri->normals[plane][1] * pointa.y + tri->normals[plane][2] * pointa.z;
	double d1 = tri->normals[plane][0] * pointb.x + tri->normals[plane][1] * pointb.y + tri->normals[plane][2] * pointb.z;
	double d2 = tri->normals[plane][0] * pointc.x + tri->normals[plane][1] * pointc.y + tri->normals[plane][2] * pointc.z;
	double d = d0;
	if (d1 < d) d = d1;
	if (d2 < d) d = d2;
	tri->constants[plane] = (int)floor( WORLD_ONE * -d );
	if (assert_front)
	{
		int x = FLOAT_TO_WORLD( assert_front->x );
		int y = FLOAT_TO_WORLD( assert_front->y );
		int z = FLOAT_TO_WORLD( assert_front->z );
		int test = tri->normals[plane][0] * x + tri->normals[plane][1] * y + tri->normals[plane][2] * z;
		assert( test + tri->constants[plane] > 0 );
	}
}

void recursive_build_skelmodel_collision( int box_index, const vec3 (*in)[3], int in_count, const float bounds[3][2], float volume )
{
	skmd_complex_collision_box &box = collision_boxes[box_index];
	box.bounds.minimum_corner.x = (int)floor(bounds[0][0] * WORLD_ONE);
	box.bounds.minimum_corner.y = (int)floor(bounds[1][0] * WORLD_ONE);
	box.bounds.minimum_corner.z = (int)floor(bounds[2][0] * WORLD_ONE);
	box.bounds.maximum_corner.x = (int)ceil(bounds[0][1] * WORLD_ONE);
	box.bounds.maximum_corner.y = (int)ceil(bounds[1][1] * WORLD_ONE);
	box.bounds.maximum_corner.z = (int)ceil(bounds[2][1] * WORLD_ONE);
	
	int best_axis = -1;
	vec3 best_plane_normal;
	float best_plane_const;
	int best_count = (in_count - MINIMUM_TRIS_IN_COLLISION_BOX) * (in_count - MINIMUM_TRIS_IN_COLLISION_BOX) + MINIMUM_TRIS_IN_COLLISION_BOX * MINIMUM_TRIS_IN_COLLISION_BOX;
	vec3 (*a_list)[3], (*b_list)[3];
	int a_count, b_count;
	float a_vol, b_vol, a_bounds[3][2], b_bounds[3][2];
	if (in_count > MINIMUM_TRIS_IN_COLLISION_BOX)
	{
		for (int test_axis = 0; test_axis < 3; ++test_axis)
		{
			float len = bounds[test_axis][1] - bounds[test_axis][0];
			int subdivisions = (int)floor(len / MINIMUM_COLLISION_BOX_SIZE);
			if (subdivisions > 10) subdivisions = 10;
			vec3 plane_norm(0,0,0);
			plane_norm[test_axis] = -1.0f;
			for (int split = 1; split < subdivisions; ++split)
			{
				float plane_const = bounds[test_axis][0] + split * len / subdivisions;
				split_tri_vert_list( in, in_count, &a_list, &a_count, &b_list, &b_count, plane_norm, plane_const );
				if (a_count < MINIMUM_TRIS_IN_COLLISION_BOX || b_count < MINIMUM_TRIS_IN_COLLISION_BOX) continue;
				a_vol = get_tri_vert_list_bounds( a_list, a_count, a_bounds );
				b_vol = get_tri_vert_list_bounds( b_list, b_count, b_bounds );
				if (a_count * a_count + b_count * b_count <= best_count && a_vol + b_vol < 1.5f * volume)
				{
					best_axis = test_axis;
					best_plane_normal = plane_norm;
					best_plane_const = plane_const;
					best_count = a_count * a_count + b_count * b_count;
				}
				
				delete [] a_list;
				delete [] b_list;
			}
		}
	}
	
	if (best_axis == -1)
	{
		box.sub_box_first_index = -1;
		box.sub_box_count = 0;
		box.triangle_first_index = collision_tri_count;
		box.triangle_count = in_count;
		
		for (int t = 0; t < in_count; ++t)
		{
			skmd_complex_collision_tri &tri = collision_tris[collision_tri_count++];
			vec3 face_normal = cross( in[t][0] - in[t][1], in[t][2] - in[t][1] );
			out_plane( &tri, 0, face_normal, in[t][0], in[t][1], in[t][2], 0 );
			out_plane( &tri, 1, cross( in[t][1] - in[t][0], face_normal ), in[t][1], in[t][0], in[t][0], &in[t][2] );
			out_plane( &tri, 2, cross( in[t][2] - in[t][1], face_normal ), in[t][2], in[t][1], in[t][1], &in[t][0] );
			out_plane( &tri, 3, cross( in[t][0] - in[t][2], face_normal ), in[t][0], in[t][2], in[t][2], &in[t][1] );
		}
	}
	else
	{
		split_tri_vert_list( in, in_count, &a_list, &a_count, &b_list, &b_count, best_plane_normal, best_plane_const );
		box.sub_box_first_index = collision_box_count;
		box.sub_box_count = 2;
		box.triangle_first_index = -1;
		box.triangle_count = 0;
		collision_box_count += 2;
		a_vol = get_tri_vert_list_bounds( a_list, a_count, a_bounds );
		b_vol = get_tri_vert_list_bounds( b_list, b_count, b_bounds );
		recursive_build_skelmodel_collision( box.sub_box_first_index, a_list, a_count, a_bounds, a_vol ); 
		recursive_build_skelmodel_collision( box.sub_box_first_index+1, b_list, b_count, b_bounds, b_vol ); 
		delete [] a_list;
		delete [] b_list;
	}
}

void clear_complex_skelmodel_collision( skmd_modelheader *mh )
{
	if (mh->complex_collision_boxes)
	{
		delete[] mh->complex_collision_boxes;
		mh->complex_collision_boxes = 0;
	}
	mh->complex_collision_box_count = 0;
	
	if (mh->complex_collision_tris)
	{
		delete[] mh->complex_collision_tris;
		mh->complex_collision_tris = 0;
	}
	mh->complex_collision_tri_count = 0;
}
	
void build_complex_skelmodel_collision( skmd_modelheader *mh )
{
	clear_complex_skelmodel_collision( mh );
	
	int vcount = mh->max_vertices;
	int i, j;
	const skmd_lodref *lodref = pick_skmd_lod( mh, &vcount );
	
	float (*pos)[4] = new float[vcount][4];
	float (*norm)[4] = new float[vcount][4];
	unsigned short (*tri)[3] = new unsigned short[lodref->data->max_triangles][3];
	object_bone *bone = new object_bone[mh->bone_count + 1];
	
	for (i = 0; i < 3; ++i) {
		bone->world_pos[i] = 0.0f;
		for (j = 0; j <3; ++j) {
			bone->m[i][j] = (i == j ? mh->model_scale : 0.0f);
		}
	}
	
	++bone;
	
	for (i = 0; i < mh->bone_count; ++i) {
		int parent = mh->bones[i].parent;
		QuatToMat( bone[i].m, mh->bones[i].float_orient );
		MatMult( bone[i].m, bone[parent].m, bone[i].m );
		for (j = 0; j < 3; ++j) {
			const float (*m)[3] = bone[parent].m;
			bone[i].world_pos[j] =  m[j][0] * mh->bones[i].float_offset[0] +
									m[j][1] * mh->bones[i].float_offset[1] +
									m[j][2] * mh->bones[i].float_offset[2] +
									bone[parent].world_pos[j];
		}
	}
	
	int tcount = 0;
	generate_skel_mesh( bone, mh->bone_count, lodref, vcount, pos, norm, tri[0], &tcount );
	
	vec3 (*tri_verts)[3] = new vec3[tcount][3];
	for (i = 0; i < tcount; ++i)
	{
		for (j = 0; j < 3; ++j)
		{
			int v = tri[i][j];
			tri_verts[i][j] = *(vec3 *)(pos[v]);
		}
	}
	
	--bone;
	delete [] bone;
	delete [] tri;
	delete [] norm;
	delete [] pos;
	
	float bounds[3][2];
	
	float volume = get_tri_vert_list_bounds( tri_verts, tcount, bounds );
	collision_box_count = 1;
	collision_tri_count = 0;
	recursive_build_skelmodel_collision( 0, tri_verts, tcount, bounds, volume );
	
	delete [] tri_verts;
	
	mh->complex_collision_box_count = collision_box_count;
	mh->complex_collision_boxes = new skmd_complex_collision_box[collision_box_count];
	memcpy( mh->complex_collision_boxes, collision_boxes, sizeof(skmd_complex_collision_box) * collision_box_count );
	
	mh->complex_collision_tri_count = collision_tri_count;
	mh->complex_collision_tris = new skmd_complex_collision_tri[collision_tri_count];
	memcpy( mh->complex_collision_tris, collision_tris, sizeof(skmd_complex_collision_tri) * collision_tri_count );
}

#pragma mark -


void defaultnew_skelmodel( tag_entry *e )
{
	skmd_tag_header head;
	memset( &head, 0, sizeof(head) );
	byte_swap_data_be( "skmd header", &head, sizeof(head), 1, bs_skmd_tag_header );
	set_entry_data( e, &head, sizeof(head), false );
}


void edit_skelmodel( tag_entry *entry )
{
	TskmdEditForm *form = new TskmdEditForm( entry );
	RegisterView( TskmdViewer );
	form->AttachWindow( KWindowSkelModel );
}


bool read_skel_model( skmd_tag_header *models, tag_entry *e )
{
	const void *data;
	unsigned length;

	if (!get_entry_data( e, &data, &length ))
		return false;
	
	byte_swap_move_be( "skmd_header", models, (void*)data, sizeof(*models), 1, bs_skmd_tag_header );
	for (int i = 0; i < models->model_count; ++i)
	{
		skmd_modelheader *buf = (skmd_modelheader *)alloc_align( models->models[i].length, 16 );
		memcpy( buf, (byte *)data + models->models[i].offset, models->models[i].length );
		unsigned hlen = import_skmd_modelheader( buf );
		skmd_modelheader *savemodel = (skmd_modelheader *)alloc_align( hlen, 16 );
		memcpy( savemodel, buf, hlen );
		setup_skmd_modelheader_ptrs( savemodel );
		for (int j = 0; j < buf->lod_count; ++j) {
			skmd_lod *lod = (skmd_lod *)alloc_align( buf->lods[j].length, 16 );
			memcpy( lod, buf->lods[j].data, buf->lods[j].length );
			int lodlen;
			lodlen = import_skmd_lod( buf->bone_count, buf->lods[j].min_vertices, buf->lods[j].max_vertices, lod );
			assert( lodlen == buf->lods[j].length );
			if (lodlen != buf->lods[j].length) {
				free_align( buf );
				models->model_count = i;	// we may leak a little memory when a file is corrupt...
				return false;
			}
			savemodel->lods[j].data = lod;
		}
		free_align( buf );
		models->models[i].model = savemodel;
	}
	
	return true;
}

void dependency_skelmodel( tag_entry *e )
{
	skmd_tag_header def;
	if( !read_skel_model( &def, e ) )
		return;
		
	add_tag_dependency( 'skan', def.animation );
	for( int i = 0; i < def.model_count; ++i )
	{
		add_tag_dependency( 'txst', def.models[i].model->texture_stack );
		
		// add attachments
		for( int j = 0; j < def.models[i].model->bone_count; j++ )
		{
			tag type = -1;
			switch ((def.models[i].model->bones[j].flags & _skmd_bone_attach_type_mask) >> _skmd_bone_attach_type_bit_0)
			{
				case _skmd_bone_attach_type_is_skelmodel:
					type = 'skmd';
					break;
				
				case _skmd_bone_attach_type_is_light:
					type = 'loli';
					break;
				
				case _skmd_bone_attach_type_is_local_projectiles:
					type = 'lpgr';
					break;
			}
			
			if (type != -1)
			{
				add_tag_dependency( type, def.models[i].model->bones[j].attachment_id );
			}
		}
		
		for( int k = 0; k < def.models[i].model->lod_count; ++k )
		{
			free_align( (void *)(def.models[i].model->lods[k].data) );
		}
		
		free_align( (void *)(def.models[i].model) );
	}
}

bool save_skel_model( skmd_tag_header *models, tag_entry *e )
{
	int i, j;
	
	int total_size = sizeof( skmd_tag_header );
	int model_size;
	int lod_size;
	int out_size;
	skmd_modelheader *m;
	skmd_lod *lod;
	
	for (i = 0; i < models->model_count; ++i) {
		total_size = (total_size + 15) & ~15;
		models->models[i].offset = total_size;
		
		m = const_cast<skmd_modelheader *>(models->models[i].model);
		
		model_size = sizeof(skmd_modelheader);
		model_size += (m->bone_count - 1) * sizeof(skmd_bone);
		model_size = (model_size + 15) & ~15;
		model_size += 2 * sizeof(float) * m->max_vertices;
		model_size += m->complex_collision_box_count * sizeof(skmd_complex_collision_box);
		model_size += m->complex_collision_tri_count * sizeof(skmd_complex_collision_tri);
		model_size += (m->max_vertices + 1 - m->hard_min_vertices) * sizeof(word);
		
		for (j = 0; j < m->lod_count; ++j) {
			model_size = (model_size + 15) & ~15;
			m->lods[j].offset = model_size;
			
			lod = const_cast<skmd_lod *>(m->lods[j].data);
			lod_size = ((size_t)(lod->weights + lod->weighted_vert_count)) - ((size_t)lod);
			m->lods[j].length = lod_size;
			model_size += lod_size;
		}
		
		models->models[i].length = model_size;
		total_size += model_size;
	}
	
	byte *tagdata = (byte *)alloc_align( total_size, 16 );
	memset( tagdata, 0, total_size );
	
	byte_swap_move_be( "skmd_header", tagdata, models, sizeof(*models), 1, bs_skmd_tag_header );
	for (i = 0; i < models->model_count; ++i) {
		m = const_cast<skmd_modelheader *>(models->models[i].model);
		model_size = ((size_t)(&m->uv[m->max_vertices])) - ((size_t)(m));
		memcpy( tagdata + models->models[i].offset, m, model_size );
		
		int collision_box_size = m->complex_collision_box_count * sizeof(skmd_complex_collision_box);
		((skmd_modelheader *)(tagdata + models->models[i].offset))->complex_collision_boxes = (skmd_complex_collision_box *)(tagdata + models->models[i].offset + model_size);
		memcpy( tagdata + models->models[i].offset + model_size, m->complex_collision_boxes, collision_box_size );
		model_size += collision_box_size;
		
		int collision_tri_size = m->complex_collision_tri_count * sizeof(skmd_complex_collision_tri);
		((skmd_modelheader *)(tagdata + models->models[i].offset))->complex_collision_tris = (skmd_complex_collision_tri *)(tagdata + models->models[i].offset + model_size);
		memcpy( tagdata + models->models[i].offset + model_size, m->complex_collision_tris, collision_tri_size );
		model_size += collision_tri_size;
		
		int real_vert_count_size = (m->max_vertices + 1 - m->hard_min_vertices) * sizeof(word);
		((skmd_modelheader *)(tagdata + models->models[i].offset))->real_vertex_counts = (word *)(tagdata + models->models[i].offset + model_size);
		memcpy( tagdata + models->models[i].offset + model_size, m->real_vertex_counts + m->hard_min_vertices, real_vert_count_size );
		model_size += real_vert_count_size;
		
		out_size = export_skmd_modelheader( (skmd_modelheader *)(tagdata + models->models[i].offset) );
		assert( out_size == model_size );
		
		for (j = 0; j < m->lod_count; ++j) {
			lod = const_cast<skmd_lod *>(m->lods[j].data);
			memcpy( tagdata + models->models[i].offset + m->lods[j].offset, m->lods[j].data, m->lods[j].length );
			out_size = export_skmd_lod( m->bone_count, m->lods[j].min_vertices, m->lods[j].max_vertices,
										(skmd_lod *)(tagdata + models->models[i].offset + m->lods[j].offset) );
			assert( out_size == m->lods[j].length );
		}
	}
	
	set_entry_data( e, tagdata, total_size, false );
	free_align( tagdata );
	
	return true;
}


BEGINDISPATCH(TskmdEditForm,TTagEditForm)
	DISPATCHTABLE(KXGControlClick,TskmdEditForm::DoControlClick)
	DISPATCHTABLE(KXGKeyClick,TskmdEditForm::DoKeyClick)
	DISPATCHTABLE(KXGListValue,TskmdEditForm::DoListValue)
	DISPATCHTABLE(KXGSliderValue,TskmdEditForm::DoSliderValue)
ENDDISPATCH


TskmdEditForm::TskmdEditForm( struct tag_entry *e ):
	TTagEditForm( e )
{
	memset( &models, 0, sizeof(models) );
	fNeedCommit = false;
}


void TskmdEditForm::free_all()
{
	for (int i = 0; i < models.model_count; ++i) {
		for (int j = 0; j < models.models[i].model->lod_count; ++j) {
			free_align( (void *)(models.models[i].model->lods[j].data) );
		}
		if (models.models[i].model->complex_collision_boxes) free(models.models[i].model->complex_collision_boxes);
		if (models.models[i].model->complex_collision_tris) free(models.models[i].model->complex_collision_tris);
		free_align( (void *)(models.models[i].model) );
	}
	models.model_count = 0;
}


TskmdEditForm::~TskmdEditForm()
{
	free_all();
}


long TskmdEditForm::DoControlClick( long arg, void * /* ptr */ )
{
	int current_model = GetInt( 6 );
	if (current_model >= models.model_count) current_model = -1;
	int i, j, k;
	
	switch (arg) {
		case 2: {
				tag newtag = pick_tag_of_type( 'skan', GetTag( 1 ), fEntry );
				if (newtag) {
					SetTag( 1, 'skan', newtag );
					SetEnabled( 3, get_entry( 'skan', newtag ) != 0 );
					models.animation = newtag;
					fNeedCommit = true;
				}
			} return 0;
		case 3: {
				tag_entry *e = get_entry( 'skan', GetTag( 1 ) );
				if (e) {
					TRevengeApp::OpenItem( e );
				}
			} return 0;
		case 4: {	// add model
				if (models.model_count < MAX_SKMD_MODELS) {
					XGFileSpecifier *fs = 0;
					
					#if OPT_MACOS
						fs = XGFileSpecifier::OpenDialog( 130 );
					#endif
					
					#if OPT_WINOS
						fs = XGFileSpecifier::OpenDialog( "Tiki Skeleton Export|*.skl|All Files|*.*","skl" );
					#endif
					
					if (fs) {
						AddModel( *fs );
						delete fs;
					}
				}
			} return 0;
		case 5: {	// delete model
				if (models.model_count > 0 && current_model >= 0 && current_model < models.model_count) {
					for (int j = 0; j < models.models[current_model].model->lod_count; ++j) {
						free_align( (void *)(models.models[current_model].model->lods[j].data) );
					}
					free_align( (void *)(models.models[current_model].model) );
					memmove( models.models + current_model, models.models + current_model + 1, models.model_count - current_model - 1 );
					--models.model_count;
					current_model = -1;
					fNeedCommit = true;
					SetMainValues();
				}
			} return 0;
		case 20: {
				if (current_model >= 0) {
					tag newtag = pick_tag_of_type( 'txst', GetTag( 19 ), fEntry );
					if (newtag) {
						SetTag( 19, 'txst', newtag );
						SetEnabled( 21, get_entry( 'txst', newtag ) != 0 );
						const_cast<skmd_modelheader *>(models.models[current_model].model)->texture_stack = newtag;
						fNeedCommit = true;
					}
				}
			} return 0;
		case 21: {
				tag_entry *e = get_entry( 'txst', GetTag( 19 ) );
				if (e) {
					TRevengeApp::OpenItem( e );
				}
			} return 0;
		case 23: { // reimport
				if( current_model >= 0 && current_model < models.model_count )
				{
					XGFileSpecifier *fs = 0;
					
					#if OPT_MACOS
						fs = XGFileSpecifier::OpenDialog( 130 );
					#endif
					
					#if OPT_WINOS
						fs = XGFileSpecifier::OpenDialog( "Tiki Skeleton Export|*.skl|All Files|*.*","skl" );
					#endif
					
					if (fs) {
						ReAddModel( *fs, current_model );
						delete fs;
					}
				}
			} return 0;
		case 46:
		case 47:
		case 48:
			{
				if (GetFlag( arg )) return 0;
				SetFlag( 46, arg == 46 );
				SetFlag( 47, arg == 47 );
				SetFlag( 48, arg == 48 );
				TskmdViewer *view = dynamic_cast<TskmdViewer *>(GetWindow()->FindViewByID( 44 ));
				if (!view) return 0;
				if (arg == 46) {
					view->SetShading( TskmdViewer::shadeWire );
				} else if (arg == 47) {
					view->SetShading( TskmdViewer::shadeFlat );
				} else {
					view->SetShading( TskmdViewer::shadeSmooth );
				}
				RedrawModelView();
			} return 0;
		case 49:
			{
				bool norm = !GetFlag( arg );
				SetFlag( arg, norm );
				TskmdViewer *view = dynamic_cast<TskmdViewer *>(GetWindow()->FindViewByID( 44 ));
				if (!view) return 0;
				view->ShowNormals( norm );
				RedrawModelView();
			} return 0;
		case 24: // attachments
			if (models.model_count > 0 && current_model >= 0 && current_model < models.model_count) {
				EditAttachments( get_entry_file(fEntry), const_cast<skmd_modelheader *>(models.models[current_model].model) );
			}
			return 0;
		case 26: // shadows
			if (models.model_count > 0 && current_model >= 0 && current_model < models.model_count) {
				EditShadows( get_entry_file(fEntry), const_cast<skmd_modelheader *>(models.models[current_model].model) );
			}
			return 0;
		case 50: // wind physics
			if (models.model_count > 0 && current_model >= 0 && current_model < models.model_count) {
				EditWindPhysics( get_entry_file(fEntry), const_cast<skmd_modelheader *>(models.models[current_model].model) );
			}
			return 0;
		case 18: { // toggle two-sided
				if (current_model >= 0) {
					bool twosided = !GetFlag( arg );
					SetFlag( arg, twosided );
					if (twosided) {
						models.models[current_model].flags |= _skmd_model_front_and_back_flag;
					} else {
						models.models[current_model].flags &= ~_skmd_model_front_and_back_flag;
					}
					TskmdViewer *view = dynamic_cast<TskmdViewer *>(GetWindow()->FindViewByID( 44 ));
					view->SetTwoSided( twosided );
					RedrawModelView();
					fNeedCommit = true;
				}
			} return 0;
		case 25: { // flip normals
				if (current_model >= 0) {
					int bone_count = models.models[current_model].model->bone_count;
					
					for (i = 0; i < models.models[current_model].model->lod_count; ++i) {
						skmd_lod *lod = const_cast<skmd_lod *>(models.models[current_model].model->lods[i].data);
						float *weights = (float *)(lod->weights);
						for (j = 0; j < bone_count; ++j) {
							int weight_count = lod->weights_per_bone[j];
							int rounded = (weight_count + 3) & ~3;
							for (k = 0; k < weight_count; ++k)
							{
								if (weights[k*4+3] == 0.0f)
								{
									weights[k*4+0] *= -1.0f;
									weights[k*4+1] *= -1.0f;
									weights[k*4+2] *= -1.0f;
								}
							}
							weights += 4 * rounded;
						}
						for (j = 0; j < lod->max_triangles; ++j) {
							int swap_vertex = lod->triangles[j][1];
							lod->triangles[j][1] = lod->triangles[j][2];
							lod->triangles[j][2] = swap_vertex;
						}
					}
					SetModelViewer( GetInt( 6 ), GetInt( 45 ) );
					fNeedCommit = true;
				}
			} return 0;
		case 40: {
				SetInt( arg, !GetInt(arg) );
				UpdateCollision( current_model );
				RedrawModelView();
			} return 0;
		case 41: { // show/hide hilight
				SetInt( arg, !GetInt(arg) );
				UpdateHilightRing( current_model );
				RedrawModelView();
			} return 0;
		case 42:
			if (models.model_count > 0 && current_model >= 0 && current_model < models.model_count) {
				build_complex_skelmodel_collision( models.models[current_model].model );
				UpdateCollision( current_model );
				RedrawModelView();
			} return 0;
		case 43:
			if (models.model_count > 0 && current_model >= 0 && current_model < models.model_count) {
				clear_complex_skelmodel_collision( models.models[current_model].model );
				UpdateCollision( current_model );
				RedrawModelView();
			} return 0;
		case 58: { // show/hide pick volume
				SetInt( arg, !GetInt(arg) );
				UpdateClickRing( current_model );
				RedrawModelView();
			} return 0;
	}
	
	return -1;
}


void TskmdEditForm::UpdateModelList()
{
	XGListControl *list = dynamic_cast<XGListControl *>(GetWindow()->FindViewByID( 6 ));
	int total = 0;
	for (int i = 0; i < models.model_count; ++i) {
		total += models.models[i].probability;
	}
	for (int i = 0; i < models.model_count; ++i) {
		char name[256];
		sprintf( name, "%s (%d/%d)", models.models[i].model->name, models.models[i].probability, total );
		list->SetItem( i, name );
	}
}


long TskmdEditForm::DoKeyClick( long arg, void * /* ptr */ )
{
	int val = GetInt( 6 );
	if (val < 0 || val >= models.model_count) return -1;
	skmd_modelref *r = models.models + val;
	skmd_modelheader *m = const_cast<skmd_modelheader *>(r->model);
	switch (arg) {
		case 7: {
				GetText( arg, m->name, sizeof(m->name) );
				fNeedCommit = true;
				UpdateModelList();
			} return 0;
		case 8: {
				r->probability = GetInt( arg );
				fNeedCommit = true;
				UpdateModelList();
			} return 0;
		case 16: {
				int min = GetInt( arg );
				if (min < m->hard_min_vertices) min = m->hard_min_vertices;
				if (min > m->max_vertices) min = m->max_vertices;
				m->user_min_vertices = min;
				SetMinVertices( val, min );
				fNeedCommit = true;
			} return 0;
		case 33: {
				m->collision_radius = GetWorldDistance( arg );
				if (GetInt( 40 )) {
					UpdateCollision( val );
					RedrawModelView();
				}
				UpdateSizes( val, false );
				fNeedCommit = true;
			} return 0;
		case 34: {
				m->collision_height0 = GetWorldDistance( arg );
				if (GetInt( 40 )) {
					UpdateCollision( val );
					RedrawModelView();
				}
				UpdateSizes( val, false );
				fNeedCommit = true;
			} return 0;
		case 35: {
				m->collision_height1 = GetWorldDistance( arg );
				if (GetInt( 40 )) {
					UpdateCollision( val );
					RedrawModelView();
				}
				UpdateSizes( val, false );
				fNeedCommit = true;
			} return 0;
		case 36: {
				m->pick_radius = GetWorldDistance( arg );
				if (GetInt( 41 )) {
					UpdateClickRing( val );
					RedrawModelView();
				}
				UpdateSizes( val, false );
				fNeedCommit = true;
			} return 0;
		case 37: {
				m->pick_height0 = GetWorldDistance( arg );
				if (GetInt( 41 )) {
					UpdateClickRing( val );
					RedrawModelView();
				}
				UpdateSizes( val, false );
				fNeedCommit = true;
			} return 0;
		case 38: {
				m->pick_height1 = GetWorldDistance( arg );
				if (GetInt( 41 )) {
					UpdateClickRing( val );
					RedrawModelView();
				}
				UpdateSizes( val, false );
				fNeedCommit = true;
			} return 0;
		case 54: {
				m->hilight_radius = GetWorldDistance( arg );
				if (GetInt( 41 )) {
					UpdateHilightRing( val );
					RedrawModelView();
				}
				UpdateSizes( val, false );
				fNeedCommit = true;
			} return 0;
		case 55: {
				m->hilight_height0 = GetWorldDistance( arg );
				if (GetInt( 41 )) {
					UpdateHilightRing( val );
					RedrawModelView();
				}
				UpdateSizes( val, false );
				fNeedCommit = true;
			} return 0;
		case 56: {
				m->hilight_height1 = GetWorldDistance( arg );
				if (GetInt( 41 )) {
					UpdateHilightRing( val );
					RedrawModelView();
				}
				UpdateSizes( val, false );
				fNeedCommit = true;
			} return 0;
		case 39: {
				m->model_scale = GetFloat( arg );
				UpdateSizes( val, GetInt( 57 ) );
				if (GetInt( 40 ) || GetInt( 41 ))
				{
					UpdateCollision( val );
					UpdateClickRing( val );
					UpdateHilightRing( val );
					RedrawModelView();
				}
				fNeedCommit = true;
			} return 0;
	}
	
	return -1;
}


long TskmdEditForm::DoListValue( long arg, void * /* ptr */ )
{
	switch (arg) {
		case 6: {	// model list
				int val = GetInt( arg );
				SetModelValues( val );
			} return 0;
	}
	
	return -1;
}


long TskmdEditForm::DoSliderValue( long arg, void *ptr )
{
	XGSValue *val = (XGSValue *)ptr;
	switch (arg) {
		case 45: {	// model list
				SetModelViewer( GetInt( 6 ), val->newVal );
			} return 0;
	}
	
	return -1;
}


void TskmdEditForm::CommitToTag()
{
#if 1
	if( save_skel_model( &models, fEntry ) )
		fNeedCommit = false;
#else
	int i, j;
	
	int total_size = sizeof( skmd_tag_header );
	int model_size;
	int lod_size;
	int out_size;
	skmd_modelheader *m;
	skmd_lod *lod;
	
	for (i = 0; i < models.model_count; ++i) {
		total_size = (total_size + 15) & ~15;
		models.models[i].offset = total_size;
		
		m = const_cast<skmd_modelheader *>(models.models[i].model);
		
		model_size = sizeof(skmd_modelheader);
		model_size += (m->bone_count - 1) * sizeof(skmd_bone);
		model_size = (model_size + 15) & ~15;
		model_size += 2 * sizeof(float) * m->max_vertices;
		
		for (j = 0; j < m->lod_count; ++j) {
			model_size = (model_size + 15) & ~15;
			m->lods[j].offset = model_size;
			
			lod = const_cast<skmd_lod *>(m->lods[j].data);
			lod_size = ((size_t)(lod->weights + lod->quadvert_count)) - ((size_t)lod);
			m->lods[j].length = lod_size;
			model_size += lod_size;
		}
		
		models.models[i].length = model_size;
		total_size += model_size;
	}
	
	byte *tagdata = (byte *)alloc_align( total_size, 16 );
	memset( tagdata, 0, total_size );
	
	byte_swap_move_be( "skmd_header", tagdata, &this->models, sizeof(this->models), 1, bs_skmd_tag_header );
	for (i = 0; i < models.model_count; ++i) {
		m = const_cast<skmd_modelheader *>(models.models[i].model);
		model_size = ((size_t)(&m->uv[m->max_vertices])) - ((size_t)(m));
		memcpy( tagdata + models.models[i].offset, m, model_size );
		out_size = export_skmd_modelheader( (skmd_modelheader *)(tagdata + models.models[i].offset) );
		assert( out_size == model_size );
		
		for (j = 0; j < m->lod_count; ++j) {
			lod = const_cast<skmd_lod *>(m->lods[j].data);
			memcpy( tagdata + models.models[i].offset + m->lods[j].offset, m->lods[j].data, m->lods[j].length );
			out_size = export_skmd_lod( m->bone_count, m->lods[j].min_vertices, m->lods[j].max_vertices,
										(skmd_lod *)(tagdata + models.models[i].offset + m->lods[j].offset) );
			assert( out_size == m->lods[j].length );
		}
	}
	
	set_entry_data( fEntry, tagdata, total_size, false );
	free_align( tagdata );
	fNeedCommit = false;
#endif
}


void TskmdEditForm::ReloadTag()
{
	free_all();
	fNeedCommit = true;
	
	if( read_skel_model( &models, fEntry ) )
	{
		for (int i = 0; i < models.model_count; ++i)
		{
			if (models.models[i].model->complex_collision_box_count)
			{
				skmd_complex_collision_box *buf = new skmd_complex_collision_box[models.models[i].model->complex_collision_box_count];
				memcpy( buf, models.models[i].model->complex_collision_boxes, models.models[i].model->complex_collision_box_count * sizeof(skmd_complex_collision_box) );
				models.models[i].model->complex_collision_boxes = buf;
			}
			else
			{
				models.models[i].model->complex_collision_boxes = 0;
			}
			if (models.models[i].model->complex_collision_tri_count)
			{
				skmd_complex_collision_tri *buf = new skmd_complex_collision_tri[models.models[i].model->complex_collision_tri_count];
				memcpy( buf, models.models[i].model->complex_collision_tris, models.models[i].model->complex_collision_tri_count * sizeof(skmd_complex_collision_tri) );
				models.models[i].model->complex_collision_tris = buf;
			}
			else
			{
				models.models[i].model->complex_collision_tris = 0;
			}
		}
		SetMainValues();
		fNeedCommit = false;
	}
}


bool TskmdEditForm::NeedCommit()
{
	return fNeedCommit;
}


void TskmdEditForm::SetMainValues( int val )
{
	SetTag( 1, 'skan', models.animation );
	SetEnabled( 3, get_entry( 'skan', models.animation ) != 0 );
	
	char buf[256];
	XGListControl *list = dynamic_cast<XGListControl *>(GetWindow()->FindViewByID( 6 ));
	if (val < 0 || val >= models.model_count) {
		val = list->GetValue();
		if (val >= 0 && val < list->Length()) {
			list->GetItem( val, buf );
			char *end = strrchr( buf, '(' );
			if (end && end > buf && end[-1] == ' ') {
				end[-1] = 0;
			}
		} else {
			buf[0] = 0;
		}
		list->DeleteAll();
		val = -1;
	} else {
		list->DeleteAll();
	}
	int total = 0;
	for (int i = 0; i < models.model_count; ++i) {
		total += models.models[i].probability;
	}
	for (int i = 0; i < models.model_count; ++i) {
		char name[256];
		sprintf( name, "%s (%d/%d, %d%%)", models.models[i].model->name, models.models[i].probability, total, (100 * models.models[i].probability) / total );
		list->Insert( list->Length(), name );
		if (val == -1 && strcmp( buf, models.models[i].model->name ) == 0) {
			val = i;
		}
	}
	if (val == -1 && models.model_count > 0) {
		val = 0;
	}
	list->SetValue( val );
	SetModelValues( val );
	SetEnabled( 4, models.model_count < MAX_SKMD_MODELS );
}


void TskmdEditForm::UpdateSizes( int which_model, bool rescale )
{
	TskmdViewer *view = dynamic_cast<TskmdViewer *>(GetWindow()->FindViewByID( 44 ));
	if (!view) return;
	
	int i, j;
	
	if (which_model >= 0 && which_model < models.model_count) {
		skmd_modelheader *mh = models.models[which_model].model;
		int vcount = mh->max_vertices;
		const skmd_lodref *lodref = pick_skmd_lod( mh, &vcount );
		
		float (*pos)[4] = new float[vcount][4];
		float (*norm)[4] = new float[vcount][4];
		unsigned short (*tri)[3] = new unsigned short[lodref->data->max_triangles][3];
		object_bone *bone = new object_bone[mh->bone_count + 1];
		
		for (i = 0; i < 3; ++i) {
			bone->world_pos[i] = 0.0f;
			for (j = 0; j <3; ++j) {
				bone->m[i][j] = (i == j ? 1.0f : 0.0f);
			}
		}
		
		++bone;
		
		for (i = 0; i < mh->bone_count; ++i) {
			int parent = mh->bones[i].parent;
			QuatToMat( bone[i].m, mh->bones[i].float_orient );
			MatMult( bone[i].m, bone[parent].m, bone[i].m );
			for (j = 0; j < 3; ++j) {
				const float (*m)[3] = bone[parent].m;
				bone[i].world_pos[j] =  m[j][0] * mh->bones[i].float_offset[0] +
										m[j][1] * mh->bones[i].float_offset[1] +
										m[j][2] * mh->bones[i].float_offset[2] +
										bone[parent].world_pos[j];
			}
		}
		
		int tcount = 0;
		generate_skel_mesh( bone, mh->bone_count, lodref, vcount, pos, norm, tri[0], &tcount );
		
		float radius = 0.0f;
		float height0 = 100000000.0f;
		float height1 = -height0;
		
		for (i = 0; i < vcount; ++i)
		{
			float r = pos[i][0] * pos[i][0] + pos[i][1] * pos[i][1];
			if (r > radius) radius = r;
			if (pos[i][2] < height0) height0 = pos[i][2];
			if (pos[i][2] > height1) height1 = pos[i][2];
		}
		
		radius = sqrtf( radius );
		
		SetFloat( 27, radius, 2 );
		SetFloat( 28, height0, 2 );
		SetFloat( 29, height1, 2 );
		SetFloat( 30, radius * mh->model_scale, 3 );
		SetFloat( 31, height0 * mh->model_scale, 3 );
		SetFloat( 32, height1 * mh->model_scale, 3 );
		
		double new_scale = GetFloat( 39 );
		if (new_scale > 0.0)
		{
			if (rescale && cached_model_scale > 0.0)
			{
				float rescale = new_scale / cached_model_scale;
				mh->collision_radius = float_to_world_distance( cached_collision_radius * rescale );
				mh->collision_height0 = float_to_world_distance( cached_collision_height0 * rescale );
				mh->collision_height1 = float_to_world_distance( cached_collision_height1 * rescale );
				mh->pick_radius = float_to_world_distance( cached_pick_radius * rescale );
				mh->pick_height0 = float_to_world_distance( cached_pick_height0 * rescale );
				mh->pick_height1 = float_to_world_distance( cached_pick_height1 * rescale );
				mh->hilight_radius = float_to_world_distance( cached_hilight_radius * rescale );
				mh->hilight_height0 = float_to_world_distance( cached_hilight_height0 * rescale );
				mh->hilight_height1 = float_to_world_distance( cached_hilight_height1 * rescale );
				SetWorldDistance( 33, mh->collision_radius );
				SetWorldDistance( 34, mh->collision_height0 );
				SetWorldDistance( 35, mh->collision_height1 );
				SetWorldDistance( 36, mh->pick_radius );
				SetWorldDistance( 37, mh->pick_height0 );
				SetWorldDistance( 38, mh->pick_height1 );
				SetWorldDistance( 54, mh->hilight_radius );
				SetWorldDistance( 55, mh->hilight_height0 );
				SetWorldDistance( 56, mh->hilight_height1 );
			}
			else
			{
				cached_collision_radius = GetFloat( 33 );
				cached_collision_height0 = GetFloat( 34 );
				cached_collision_height1 = GetFloat( 35 );
				cached_pick_radius = GetFloat( 36 );
				cached_pick_height0 = GetFloat( 37 );
				cached_pick_height1 = GetFloat( 38 );
				cached_hilight_radius = GetFloat( 54 );
				cached_hilight_height0 = GetFloat( 55 );
				cached_hilight_height1 = GetFloat( 56 );
				cached_model_scale = new_scale;
			}
		}
		
		--bone;
		delete [] bone;
		delete [] tri;
		delete [] norm;
		delete [] pos;
	} else {
		SetText( 27, "--" );
		SetText( 28, "--" );
		SetText( 29, "--" );
		SetText( 30, "--" );
		SetText( 31, "--" );
		SetText( 32, "--" );
		
	}
}


void probe_collision_tree( skmd_modelheader *m, int box_index, int *min, float *avg, int *max )
{
	skmd_complex_collision_box &b = m->complex_collision_boxes[box_index];
	if (!b.sub_box_count)
	{
		*min = 1;
		*avg = 1.0f;
		*max = 1;
	}
	else
	{
		probe_collision_tree( m, b.sub_box_first_index, min, avg, max );
		for (int i = 1; i < b.sub_box_count; ++i)
		{
			int temp_min, temp_max;
			float temp_avg;
			probe_collision_tree( m, b.sub_box_first_index + i, &temp_min, &temp_avg, &temp_max );
			if (temp_min < *min) *min = temp_min;
			if (temp_max > *max) *max = temp_max;
			*avg += temp_avg;
		}
		*min += 1;
		*max += 1;
		*avg = 1.0f + *avg / b.sub_box_count;
	}
}


void TskmdEditForm::UpdateCollision( int val )
{
	TskmdViewer *view = dynamic_cast<TskmdViewer *>(GetWindow()->FindViewByID( 44 ));
	if (!view) return;
	
	if (val >= 0 && val < models.model_count)
	{
		skmd_modelheader *m = const_cast<skmd_modelheader *>(models.models[val].model);
		if (m->complex_collision_boxes && m->complex_collision_box_count)
		{
			SetEnabled( 43, true ); // clear
			int min, max;
			float avg;
			probe_collision_tree( m, 0, &min, &avg, &max );
			SetInt( 51, m->complex_collision_tri_count );
			SetInt( 52, m->complex_collision_box_count );
			char buf[40];
			sprintf( buf, "%d, %.1f, %d", min, avg, max );
			SetText( 53, buf );
		}
		else
		{
			SetEnabled( 43, false );
			SetText( 51, "--" );
			SetText( 52, "--" );
			SetText( 53, "--" );
		}
		if (GetInt(40))
		{
			view->SetCollision( m->complex_collision_boxes, m->complex_collision_box_count, m->collision_radius, m->collision_height0, m->collision_height1, 1.0 / (512 * m->model_scale) );
		}
		else
		{
			view->SetCollision( 0, 0, -1, 0, 0, 0.0f );
		}
	}
	else
	{
		view->SetCollision( 0, 0, -1, 0, 0, 0.0f );
		SetEnabled( 43, false );
		SetText( 51, "--" );
		SetText( 52, "--" );
		SetText( 53, "--" );
	}
}


void TskmdEditForm::UpdateHilightRing( int val )
{
	TskmdViewer *view = dynamic_cast<TskmdViewer *>(GetWindow()->FindViewByID( 44 ));
	if (!view) return;
	
	if (val >= 0 && val < models.model_count)
	{
		skmd_modelheader *m = const_cast<skmd_modelheader *>(models.models[val].model);
		if (GetInt(41))
		{
			view->SetHilight( m->hilight_radius, m->hilight_height0, m->hilight_height1, 1.0 / (512 * m->model_scale) );
		}
		else
		{
			view->SetHilight( -1, 0, 0, 0.0f );
		}
	}
	else
	{
		view->SetHilight( -1, 0, 0, 0.0f );
	}
}


void TskmdEditForm::UpdateClickRing( int val )
{
	TskmdViewer *view = dynamic_cast<TskmdViewer *>(GetWindow()->FindViewByID( 44 ));
	if (!view) return;
	
	if (val >= 0 && val < models.model_count)
	{
		skmd_modelheader *m = const_cast<skmd_modelheader *>(models.models[val].model);
		if (GetInt(58))
		{
			view->SetClick( m->pick_radius, m->pick_height0, m->pick_height1, 1.0 / (512 * m->model_scale) );
		}
		else
		{
			view->SetClick( -1, 0, 0, 0.0f );
		}
	}
	else
	{
		view->SetClick( -1, 0, 0, 0.0f );
	}
}


void TskmdEditForm::RedrawModelView()
{
	TskmdViewer *view = dynamic_cast<TskmdViewer *>(GetWindow()->FindViewByID( 44 ));
	if (!view) return;
	view->DoDrawView( view->GetContentRect() );
}


void TskmdEditForm::SetModelValues( int val )
{
	if (val >= 0 && val < models.model_count) {
		skmd_modelheader *m = const_cast<skmd_modelheader *>(models.models[val].model);
		SetEnabled( 5, true );
		SetEnabled( 7, true );
		SetText( 7, m->name );
		SetEnabled( 8, true );
		SetInt( 8, models.models[val].probability );
		SetInt( 9, m->bone_count );
		SetInt( 10, m->max_vertices );
		int tri = 0;
		for (int i = 0; i < m->lod_count; ++i) {
			if (m->lods[i].data->max_triangles > tri) {
				tri = m->lods[i].data->max_triangles;
			}
		}
		SetInt( 11, tri );
		SetInt( 12, m->hard_min_vertices );
		tri = 500000;
		for (int i = 0; i < m->lod_count; ++i) {
			if (m->lods[i].data->max_triangles < tri) {
				tri = m->lods[i].data->max_triangles;
			}
		}
		SetInt( 13, tri );
		if (m->user_min_vertices < m->hard_min_vertices) {
			m->user_min_vertices = m->hard_min_vertices;
		}
		if (m->user_min_vertices > m->max_vertices) {
			m->user_min_vertices = m->max_vertices;
		}
		SetEnabled( 16, true );
		SetInt( 16, m->user_min_vertices );
		SetMinVertices( val, m->user_min_vertices ); // also sets 17 (min tri)
		SetEnabled( 18, true );
		SetFlag( 18, models.models[val].flags & _skmd_model_front_and_back_flag );
		
		SetTag( 19, 'txst', m->texture_stack );
		SetEnabled( 20, true );
		if (get_entry( 'txst', m->texture_stack ) != 0) {
			SetEnabled( 21, true );
			SetEnabled( 22, true );
			XGPopControl *pop = dynamic_cast<XGPopControl *>(GetWindow()->FindViewByID( 22 ));
			pop->DeleteAll();
			pop->Insert( 0, "(texture off)" );
			pop->Insert( 1, "-" );
			for (int i = 0; i < 4; ++i) {
				char buf[40];
				sprintf( buf, "Stack %d", i );
				pop->Insert( i+2, buf );
			}
			pop->SetValue( 0 );
		} else {
			SetEnabled( 21, false );
			SetEnabled( 22, false );
			XGPopControl *pop = dynamic_cast<XGPopControl *>(GetWindow()->FindViewByID( 22 ));
			pop->DeleteAll();
			pop->Insert( 0, "(texture off)" );
			pop->SetValue( 0 );
		}
		
		SetEnabled( 23, true );
		SetEnabled( 24, true );
		SetEnabled( 26, true );
		SetEnabled( 50, true );
		SetEnabled( 25, true );
		
		SetEnabled( 33, true );
		SetWorldDistance( 33, m->collision_radius );
		SetEnabled( 34, true );
		SetWorldDistance( 34, m->collision_height0 );
		SetEnabled( 35, true );
		SetWorldDistance( 35, m->collision_height1 );
		
		SetEnabled( 36, true );
		SetWorldDistance( 36, m->pick_radius );
		SetEnabled( 37, true );
		SetWorldDistance( 37, m->pick_height0 );
		SetEnabled( 38, true );
		SetWorldDistance( 38, m->pick_height1 );
		
		SetEnabled( 54, true );
		SetWorldDistance( 54, m->hilight_radius );
		SetEnabled( 55, true );
		SetWorldDistance( 55, m->hilight_height0 );
		SetEnabled( 56, true );
		SetWorldDistance( 56, m->hilight_height1 );
		
		SetEnabled( 39, true );
		SetFloat( 39, m->model_scale, 6 );
		
		UpdateSizes( val, false );
		
		SetEnabled( 42, true );
		
		SetEnabled( 40, true );
		SetEnabled( 41, true );
		SetEnabled( 46, true );
		SetEnabled( 47, true );
		SetEnabled( 48, true );
		SetEnabled( 49, true );
		
		SetEnabled( 57, true );
	} else {
		SetEnabled( 5, false );
		SetEnabled( 7, false );
		SetText( 7, "" );
		SetEnabled( 8, false );
		SetText( 8, "" );
		SetText( 9, "--" );
		SetText( 10, "--" );
		SetText( 11, "--" );
		SetText( 12, "--" );
		SetText( 13, "--" );
		SetText( 14, "--" );
		SetText( 15, "--" );
		SetEnabled( 16, false );
		SetText( 16, "" );
		SetText( 17, "--" );
		SetEnabled( 18, false );
		SetInt( 18, 0 );
		SetText( 19, "" );
		SetEnabled( 20, false );
		SetEnabled( 21, false );
		SetEnabled( 22, false );
		XGPopControl *pop = dynamic_cast<XGPopControl *>(GetWindow()->FindViewByID( 22 ));
		pop->DeleteAll();
		pop->Insert( 0, "(texture off)" );
		pop->SetValue( 0 );
		SetEnabled( 23, false );
		SetEnabled( 24, false );
		SetEnabled( 26, false );
		SetEnabled( 50, false );
		SetEnabled( 25, false );
		SetEnabled( 33, false );
		
		SetText( 33, "" );
		SetEnabled( 34, false );
		SetText( 34, "" );
		SetEnabled( 35, false );
		SetText( 35, "" );
		
		SetEnabled( 36, false );
		SetText( 36, "" );
		SetEnabled( 37, false );
		SetText( 37, "" );
		SetEnabled( 38, false );
		SetText( 38, "" );
		
		SetEnabled( 54, false );
		SetText( 54, "" );
		SetEnabled( 55, false );
		SetText( 55, "" );
		SetEnabled( 56, false );
		SetText( 56, "" );
		
		SetEnabled( 39, false );
		SetText( 39, "" );
		
		UpdateSizes( val, false );
		
		SetEnabled( 42, false );
		
		SetEnabled( 40, false );
		SetEnabled( 41, false );
		SetEnabled( 46, false );
		SetEnabled( 47, false );
		SetEnabled( 48, false );
		SetEnabled( 49, false );
		
		SetEnabled( 57, false );
	}
	
	SetModelViewer( val, 100000 );
}


void TskmdEditForm::SetMinVertices( int val, int min )
{
	if (val >= 0 && val < models.model_count) {
		const skmd_modelheader *m = models.models[val].model;
		if (min < m->hard_min_vertices) min = m->hard_min_vertices;
		if (min > m->max_vertices) min = m->max_vertices;
		SetText( 17, "??" );
		const skmd_lodref *lod = pick_skmd_lod( m, &min );
		if (lod)
		{
			int tri = 0;
			for (int j = 0; j < lod->data->max_triangles; ++j) {
				if (lod->data->min_vertices_for_tri[j] <= min) {
					++tri;
				}
			}
			SetInt( 17, tri );
		}
	} else {
		SetText( 17, "--" );
	}
}


void TskmdEditForm::AddModel( XGFileSpecifier &fs )
{
	if (models.model_count >= MAX_SKMD_MODELS) return;
	
	skmd_modelheader *mh = create_skmd_from_skl( fs );
	if (mh) {
		skmd_modelref &ref = models.models[models.model_count++];
		ref.flags = 0;
		ref.probability = 60;
		ref.model = mh;
		
		SetMainValues( models.model_count - 1 );
		
		fNeedCommit = true;
	}
}

void TskmdEditForm::ReAddModel( XGFileSpecifier &fs, int index )
{
	skmd_modelheader *mh = create_skmd_from_skl( fs );
	int ii;
	
	if( mh )
	{
		skmd_modelref &ref = models.models[index];
		
		// name
		strcpy( mh->name, ref.model->name );

		// probability & flags
		
		// texture stack
		mh->texture_stack = ref.model->texture_stack;
		
		if (ref.model->user_min_vertices >= mh->hard_min_vertices && ref.model->user_min_vertices <= mh->max_vertices)
		{
			mh->user_min_vertices = ref.model->user_min_vertices;
		}
		
		// model scale
		mh->model_scale = ref.model->model_scale;
		
		mh->collision_radius = ref.model->collision_radius;
		mh->collision_height0 = ref.model->collision_height0;
		mh->collision_height1 = ref.model->collision_height1;
		mh->pick_radius = ref.model->pick_radius;
		mh->pick_height0 = ref.model->pick_height0;
		mh->pick_height1 = ref.model->pick_height1;
		mh->hilight_radius = ref.model->hilight_radius;
		mh->hilight_height0 = ref.model->hilight_height0;
		mh->hilight_height1 = ref.model->hilight_height1;
		
		if (ref.model->complex_collision_tri_count)
		{
			build_complex_skelmodel_collision( mh );
		}
		
		// attachments
		if( mh->bone_count == ref.model->bone_count )
		{
			for( ii = 0; ii < mh->bone_count; ii++ )
			{
				mh->bones[ii].flags = ref.model->bones[ii].flags;
				mh->bones[ii].attachment_id = ref.model->bones[ii].attachment_id;
				mh->bones[ii].shadow_near = ref.model->bones[ii].shadow_near;
				mh->bones[ii].shadow_far = ref.model->bones[ii].shadow_far;
				mh->bones[ii].shadow_width = ref.model->bones[ii].shadow_width;
				mh->bones[ii].custom_shadow_tag = ref.model->bones[ii].custom_shadow_tag;
				mh->bones[ii].custom_shadow_stack = ref.model->bones[ii].custom_shadow_stack;
				mh->bones[ii].custom_shadow_level = ref.model->bones[ii].custom_shadow_level;
				mh->bones[ii].tree_mass = ref.model->bones[ii].tree_mass;
				mh->bones[ii].tree_resistance = ref.model->bones[ii].tree_resistance;
				mh->bones[ii].tree_dampening = ref.model->bones[ii].tree_dampening;
			}
		}

		ref.model = mh;
		
		SetMainValues( index );
		fNeedCommit = true;
	}
}



void TskmdEditForm::SetModelViewer( int which_model, int vertices )
{
	static bool no_recurse = false;
	
	if (no_recurse) return;
	
	TskmdViewer *view = dynamic_cast<TskmdViewer *>(GetWindow()->FindViewByID( 44 ));
	if (!view) return;
	
	int i, j;
	
	if (which_model >= 0 && which_model < models.model_count) {
		const skmd_modelheader *mh = models.models[which_model].model;
		int vcount = vertices;
		const skmd_lodref *lodref = pick_skmd_lod( mh, &vcount );
		
		float (*pos)[4] = new float[vcount][4];
		float (*norm)[4] = new float[vcount][4];
		unsigned short (*tri)[3] = new unsigned short[lodref->data->max_triangles][3];
		object_bone *bone = new object_bone[mh->bone_count + 1];
		
		for (i = 0; i < 3; ++i) {
			bone->world_pos[i] = 0.0f;
			for (j = 0; j <3; ++j) {
				bone->m[i][j] = (i == j ? 1.0f : 0.0f);
			}
		}
		
		++bone;
		
		for (i = 0; i < mh->bone_count; ++i) {
			int parent = mh->bones[i].parent;
			QuatToMat( bone[i].m, mh->bones[i].float_orient );
			MatMult( bone[i].m, bone[parent].m, bone[i].m );
			//QuatMult( bone[i].q, mh->bones[i].orient, bone[mh->bones[i].parent].q );
			//QuatToMat( bone[i].m, bone[i].q );
			for (j = 0; j < 3; ++j) {
				const float (*m)[3] = bone[parent].m;
				bone[i].world_pos[j] =  m[j][0] * mh->bones[i].float_offset[0] +
										m[j][1] * mh->bones[i].float_offset[1] +
										m[j][2] * mh->bones[i].float_offset[2] +
										bone[parent].world_pos[j];
			}
		}
		
		int tcount = 0;
		generate_skel_mesh( bone, mh->bone_count, lodref, vcount, pos, norm, tri[0], &tcount );
		view->SetData( pos, norm, mh->uv, vcount, tri, tcount );
		view->SetTwoSided( models.models[which_model].flags & _skmd_model_front_and_back_flag );
		
		XGSlideControl *slider = dynamic_cast<XGSlideControl *>(GetWindow()->FindViewByID( 45 ));
		if (slider) {
			slider->ShowView();
			no_recurse = true;
			slider->SetMinValue( mh->hard_min_vertices );
			slider->SetMaxValue( mh->max_vertices );
			slider->SetValue( vcount );
			no_recurse = false;
		}
		SetInt( 14, vcount );
		SetInt( 15, tcount );
		GetWindow()->UpdateView();
		--bone;
		delete [] bone;
		delete [] tri;
		delete [] norm;
		delete [] pos;
		
		UpdateCollision( which_model );
		UpdateHilightRing( which_model );
		UpdateClickRing( which_model );
	} else {
		view->SetData( 0, 0, 0, 0, 0, 0 );
		GetWindow()->FindViewByID( 45 )->HideView();
		SetText( 14, "--" );
		SetText( 15, "--" );
		UpdateClickRing( which_model );
		UpdateHilightRing( which_model );
		UpdateCollision( which_model );
	}
	RedrawModelView();
}


bool ExtractSKMDNames( tag_entry *e, XGStringList *out )
{
	out->DeleteAll();
	if (!e) {
		out->Insert( 0, "(model missing)" );
		return false;
	}
	const void *tagdata;
	unsigned taglen;
	if (!get_entry_data( e, &tagdata, &taglen )) {
		out->Insert( 0, "(model damaged)" );
		return false;
	}
	skmd_tag_header header;
	byte_swap_move_be( "skmd header", &header, (void *)tagdata, sizeof(header), 1, bs_skmd_tag_header );
	return ExtractSKANNames( get_entry( 'skan', header.animation ), out );
}


struct attach_data {
	int type;
	tag id;
};

struct attach_dialog_data {
	tag_file *source;
	skmd_modelheader *model;
	attach_data *data;
};

static void rebuild_attach_list( XGListControl *list, attach_dialog_data *add )
{
	int i;
	
	int val = list->GetValue();
	list->DeleteAll();
	for (i = 0; i < add->model->bone_count; ++i) {
		char buf[256];
		strcpy( buf, add->model->bones[i].name );
		if (add->data[i].id != -1) {
			strcat( buf, " -> " );
			tag type;
			switch (add->data[i].type) {
				default:
				case _skmd_bone_attach_type_is_skelmodel:
					type = 'skmd';
					break;
				case _skmd_bone_attach_type_is_light:
					strcat( buf, "LIGHT " );
					type = 'loli';
					break;
				case _skmd_bone_attach_type_is_local_projectiles:
					strcat( buf, "PROJECTILES " );
					type = 'lpgr';
					break;
			}
			tag_entry *e = get_entry( type, add->data[i].id );
			if (e) {
				sprintf( buf + strlen(buf), "%s '%s'", get_entry_name(e), tagstr(add->data[i].id), tagstr(type) );
			} else {
				sprintf( buf + strlen(buf), "(missing tag '%s')", tagstr(add->data[i].id), tagstr(type) );
			}
		}
		list->Insert( i, buf );
	}
	list->SetValue( val );
}

static long EditAttachProc( XGDialog *dlog, void *data, long msg, long arg, void * /*parg*/ )
{
	if (!dlog) return -1;
	attach_dialog_data *add = (attach_dialog_data *)data;
	XGListControl *list = dynamic_cast<XGListControl *>(dlog->FindViewByID(1));
	if (!add || !list) return -1;
	int val = list->GetValue();
	
	switch (msg) {
		case KXGDialogMessage:
			switch (arg) {
				case KXGDMInitDialog: {
						list->SetValue( -1 );
						rebuild_attach_list( list, add );
					} break;
				default:
					break;
			} break;
		case KXGControlClick:
			switch (arg) {
				case 3:		// none
					dlog->EndDialog(1);
					return 0;
				case 4:		// cancel
					dlog->EndDialog(0);
					return 0;
				case 10:	// model
					if (val >= 0 && val < add->model->bone_count) {
						tag newmodel = pick_tag_of_type( 'skmd', add->data[val].id, add->source );
						if (newmodel) {
							add->data[val].type = _skmd_bone_attach_type_is_skelmodel;
							add->data[val].id = newmodel;
						}
						rebuild_attach_list( list, add );
					}
					return 0;
				case 11:	// light
					if (val >= 0 && val < add->model->bone_count) {
						tag newlight = pick_tag_of_type( 'loli', add->data[val].id, add->source );
						if (newlight) {
							add->data[val].type = _skmd_bone_attach_type_is_light;
							add->data[val].id = newlight;
						}
						rebuild_attach_list( list, add );
					}
					return 0;
				case 12:	// projectile
					if (val >= 0 && val < add->model->bone_count) {
						tag newproj = pick_tag_of_type( 'lpgr', add->data[val].id, add->source );
						if (newproj) {
							add->data[val].type = _skmd_bone_attach_type_is_local_projectiles;
							add->data[val].id = newproj;
						}
						rebuild_attach_list( list, add );
					}
					return 0;
				case 13:	// none
					if (val >= 0 && val < add->model->bone_count) {
						add->data[val].id = -1;
						rebuild_attach_list( list, add );
					}
					return 0;
			} break;
		default:
			break;
	}
	
	return -1;
}


void TskmdEditForm::EditAttachments( tag_file *source, skmd_modelheader *model )
{
	attach_dialog_data add;
	int i;
	
	add.source = source;
	add.model = model;
	add.data = new attach_data[model->bone_count];
	for (i = 0; i < model->bone_count; ++i) {
		add.data[i].type = (model->bones[i].flags & _skmd_bone_attach_type_mask) >> _skmd_bone_attach_type_bit_0;
		add.data[i].id = model->bones[i].attachment_id;
	}
	
	bool ok = XGDialog::ModalDialog( KWindowModelAttachments, EditAttachProc, &add );
	if (ok) {
		for (i = 0; i < model->bone_count; ++i) {
			model->bones[i].flags &= ~(_skmd_bone_attach_type_mask);
			model->bones[i].flags |= (add.data[i].type << _skmd_bone_attach_type_bit_0);
			model->bones[i].attachment_id = add.data[i].id;
		}
		fNeedCommit = true;
	}
	
	delete[] add.data;
}


struct shadow_data {
	int axis;
	float near_dist, far_dist, width;
	vec3 pos;
	float m[3][3];
};

struct shadow_dialog_data {
	tag_file *source;
	skmd_modelheader *model;
	shadow_data *data;
};


static void rebuild_shadow_list( XGListControl *list, TskmdViewer *preview, shadow_dialog_data *sdd )
{
	int i, j;
	
	skmdViewerCylinder *cyls = new skmdViewerCylinder[sdd->model->bone_count];
	int shadow_count = 0;
	
	int val = list->GetValue();
	list->DeleteAll();
	for (i = 0; i < sdd->model->bone_count; ++i) {
		char buf[256];
		strcpy( buf, sdd->model->bones[i].name );
		if (sdd->data[i].axis < 3) {
			sprintf( buf+strlen(buf), " -> %c-axis, near: %.2f, far: %.2f, radius: %.2f",
						sdd->data[i].axis + 'X', sdd->data[i].near_dist, sdd->data[i].far_dist, sdd->data[i].width );
			
			if (i == val)
			{
				cyls[shadow_count].rgba[0] = 1.0f;
				cyls[shadow_count].rgba[1] = 1.0f;
				cyls[shadow_count].rgba[2] = 0.0f;
				cyls[shadow_count].rgba[3] = 1.0f;
			}
			else
			{
				cyls[shadow_count].rgba[0] = 0.3f;
				cyls[shadow_count].rgba[1] = 0.3f;
				cyls[shadow_count].rgba[2] = 0.3f;
				cyls[shadow_count].rgba[3] = 0.5f;
			}
			cyls[shadow_count].base = sdd->data[i].pos;
			for (j = 0; j < 3; ++j)
			{
				cyls[shadow_count].axis[j] = sdd->data[i].m[j][sdd->data[i].axis];
			}
			cyls[shadow_count].r = sdd->data[i].width;
			cyls[shadow_count].h0 = sdd->data[i].near_dist - sdd->data[i].width;
			cyls[shadow_count].h1 = sdd->data[i].far_dist + sdd->data[i].width;
			cyls[shadow_count].segments = 13;
			shadow_count += 1;
		}
		list->Insert( i, buf );
	}
	list->SetValue( val );
	
	if (preview)
	{
		preview->SetShadowCylinders( cyls, shadow_count );
		preview->DoDrawView( preview->GetContentRect() );
	}
	delete [] cyls;
}

void update_shadow_sel( XGDialog *dlog, shadow_dialog_data *sdd )
{
	XGListControl *list = dynamic_cast<XGListControl *>(dlog->FindViewByID(1));
	TskmdViewer *preview = dynamic_cast<TskmdViewer *>(dlog->FindViewByID(5));
	if (!sdd || !list) return;
	int val = list->GetValue();
	int i;
	
	if (val >= 0 && val < sdd->model->bone_count)
	{
		char buf[256];
		
		for (i = 0; i < 3; ++i)
		{
			sprintf( buf, "%.2f .. %.2f", sdd->model->bones[val].bounds[i][0], sdd->model->bones[val].bounds[i][1] );
			dlog->SetText( 10 + i, buf );
		}
		for (i = 0; i < 4; ++i)
		{
			dlog->SetEnabled( 20+i, true );
			dlog->SetValue( 20+i, i == sdd->data[val].axis );
		}
		SetFloat( dlog, 30, sdd->data[val].width, 4 );
		SetFloat( dlog, 31, sdd->data[val].near_dist, 4 );
		SetFloat( dlog, 32, sdd->data[val].far_dist, 4 );
		dlog->SetEnabled( 30, true );
		dlog->SetEnabled( 31, true );
		dlog->SetEnabled( 32, true );
	}
	else
	{
		for (i = 0; i < 3; ++i)
		{
			dlog->SetText( 10+i, "" );
			dlog->SetText( 30+i, "" );
			dlog->SetEnabled( 30+i, false );
		}
		for (i = 0; i < 4; ++i)
		{
			dlog->SetValue( 20+i, 0 );
			dlog->SetEnabled( 20+i, false );
		}
	}
	
	rebuild_shadow_list( list, preview, sdd );
}

static long EditShadowProc( XGDialog *dlog, void *data, long msg, long arg, void * /*parg*/ )
{
	if (!dlog) return -1;
	shadow_dialog_data *sdd = (shadow_dialog_data *)data;
	XGListControl *list = dynamic_cast<XGListControl *>(dlog->FindViewByID(1));
	TskmdViewer *preview = dynamic_cast<TskmdViewer *>(dlog->FindViewByID(5));
	if (!sdd || !list || !preview) return -1;
	int val = list->GetValue();
	int i;
	
	switch (msg) {
		case KXGDialogMessage:
			switch (arg) {
				case KXGDMInitDialog: {
						list->SetValue( -1 );
						update_shadow_sel( dlog, sdd );
					} break;
				default:
					break;
			} break;
		case KXGControlClick:
			switch (arg) {
				case 3:		// none
					dlog->EndDialog(1);
					return 0;
				case 4:		// cancel
					dlog->EndDialog(0);
					return 0;
				case 20:
				case 21:
				case 22:
				case 23:
					if (val >= 0)
					{
						for (i = 0; i < 4; ++i)
						{
							dlog->SetValue( 20+i, i == arg );
						}
						sdd->data[val].axis = arg - 20;
						rebuild_shadow_list( list, preview, sdd );
					}
					return 0;
			} break;
		case KXGListValue:
			switch (arg) {
				case 1:
					update_shadow_sel( dlog, sdd );
					return 0;
			} break;
		case KXGKeyClick:
			switch (arg) {
				case 30:
					if (val >= 0)
					{
						sdd->data[val].width = GetFloat( dlog, arg );
						rebuild_shadow_list( list, preview, sdd );
					}
					return 0;
					
				case 31:
					if (val >= 0)
					{
						sdd->data[val].near_dist = GetFloat( dlog, arg );
						rebuild_shadow_list( list, preview, sdd );
					}
					return 0;
					
				case 32:
					if (val >= 0)
					{
						sdd->data[val].far_dist = GetFloat( dlog, arg );
						rebuild_shadow_list( list, preview, sdd );
					}
					return 0;
					
			}
		default:
			break;
	}
	
	return -1;
}


void TskmdEditForm::EditShadows( tag_file *source, skmd_modelheader *model )
{
	static const float ident[3][3] = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } };
	shadow_dialog_data sdd;
	int i, j;
	
	sdd.source = source;
	sdd.model = model;
	sdd.data = new shadow_data[model->bone_count];
	shadow_data root_bone;
	root_bone.pos = vec3( 0, 0, 0 );
	memcpy( root_bone.m, ident, sizeof(root_bone.m) );
	for (i = 0; i < model->bone_count; ++i)
	{
		sdd.data[i].axis = (model->bones[i].flags & _skmd_bone_shadow_dimension_mask) >> _skmd_bone_shadow_dimension_bit_0;
		sdd.data[i].near_dist = model->bones[i].shadow_near;
		sdd.data[i].far_dist = model->bones[i].shadow_far;
		sdd.data[i].width = model->bones[i].shadow_width;
		shadow_data *parent = &root_bone;
		if (model->bones[i].parent >= 0) parent = sdd.data + model->bones[i].parent;
		QuatToMat( sdd.data[i].m, model->bones[i].float_orient );
		MatMult( sdd.data[i].m, parent->m, sdd.data[i].m );
		for (j = 0; j < 3; ++j)
		{
			sdd.data[i].pos[j] =	parent->m[j][0] * model->bones[i].float_offset[0] +
									parent->m[j][1] * model->bones[i].float_offset[1] +
									parent->m[j][2] * model->bones[i].float_offset[2] +
									parent->pos[j];
		}
	}
	
	
	bool ok = XGDialog::ModalDialog( KWindowModelShadows, EditShadowProc, &sdd );
	if (ok) {
		for (i = 0; i < model->bone_count; ++i) {
			model->bones[i].flags &= ~(_skmd_bone_shadow_dimension_mask);
			model->bones[i].flags |= (sdd.data[i].axis << _skmd_bone_shadow_dimension_bit_0);
			model->bones[i].shadow_near = sdd.data[i].near_dist;
			model->bones[i].shadow_far = sdd.data[i].far_dist;
			model->bones[i].shadow_width = sdd.data[i].width;
		}
		fNeedCommit = true;
	}
	
	delete[] sdd.data;
}

void TskmdEditForm::EditWindPhysics( tag_file * /* source */, skmd_modelheader * /* model */ )
{
}


bool replace_model_data_at_index( tag_entry *e, XGFileSpecifier *fs, int index )
{
	skmd_tag_header models;
	
	if( read_skel_model( &models, e ) )
	{
		skmd_modelheader *mh = create_skmd_from_skl( *fs );
		int ii;

		if( mh )
		{
			skmd_modelref &ref = models.models[index];
			
			// name
			strcpy( mh->name, ref.model->name );

			// probability & flags

			// texture stack
			mh->texture_stack = ref.model->texture_stack;
			
			// model scale
			mh->model_scale = ref.model->model_scale;
			
			// attachments
			if( mh->bone_count == ref.model->bone_count )
			{
				for( ii = 0; ii < mh->bone_count; ii++ )
				{
					mh->bones[ii].flags = ref.model->bones[ii].flags;
					mh->bones[ii].attachment_id = ref.model->bones[ii].attachment_id;
					mh->bones[ii].shadow_near = ref.model->bones[ii].shadow_near;
					mh->bones[ii].shadow_far = ref.model->bones[ii].shadow_far;
					mh->bones[ii].shadow_width = ref.model->bones[ii].shadow_width;
					mh->bones[ii].custom_shadow_tag = ref.model->bones[ii].custom_shadow_tag;
					mh->bones[ii].custom_shadow_stack = ref.model->bones[ii].custom_shadow_stack;
					mh->bones[ii].custom_shadow_level = ref.model->bones[ii].custom_shadow_level;
					mh->bones[ii].tree_mass = ref.model->bones[ii].tree_mass;
					mh->bones[ii].tree_resistance = ref.model->bones[ii].tree_resistance;
					mh->bones[ii].tree_dampening = ref.model->bones[ii].tree_dampening;
				}
			}

			ref.model = mh;
		}
		
		return save_skel_model( &models, e );
	}
	
	return false;
}
