Index: /trunk/legacy/keyframed_mesh.cc
===================================================================
--- /trunk/legacy/keyframed_mesh.cc	(revision 480)
+++ /trunk/legacy/keyframed_mesh.cc	(revision 480)
@@ -0,0 +1,252 @@
+// Copyright (C) 2011-2015 ChaosForge Ltd
+// http://chaosforge.org/
+//
+// This file is part of Nova libraries. 
+// For conditions of distribution and use, see copying.txt file in root folder.
+
+#include "nv/gfx/keyframed_mesh.hh"
+
+#include "nv/interface/context.hh"
+#include "nv/interface/device.hh"
+#include "nv/core/logging.hh"
+
+using namespace nv;
+
+#if 0
+
+nv::keyframed_mesh::keyframed_mesh( context* a_context, const data_channel_set* a_data, const mesh_nodes_data* a_tag_map )
+	: animated_mesh()
+	, m_context( a_context )
+	, m_tag_map( a_tag_map )
+	, m_last_frame( 0 )
+	, m_next_frame( 0 )
+	, m_interpolation( 0.0f )
+	, m_active( false )
+{
+	m_index_count   = a_data->get_channel_size( slot::INDEX );
+	m_vertex_count  = a_data->get_channel_size<vertex_t>();
+	uint32 pos_size = a_data->get_channel_size<vertex_pnt>();
+	if ( pos_size == 0 )
+	{
+		pos_size      = a_data->get_channel_size<vertex_pn>();
+		m_has_tangent = false;
+		m_vsize       = sizeof( vertex_pn );
+	}
+	else
+	{
+		m_has_tangent = true;
+		m_vsize       = sizeof( vertex_pnt );
+	}
+	m_frame_count  = pos_size / m_vertex_count;
+	m_pbuffer      = buffer();
+
+	if ( m_tag_map && m_tag_map->size() > 0 )
+	{
+		m_interpolation_key = (*m_tag_map)[ 0 ]->get_interpolation_key();
+	}
+}
+
+nv::size_t keyframed_mesh::get_max_frames() const
+{
+	return m_frame_count;
+}
+
+transform keyframed_mesh::get_node_transform( uint32 node_id ) const
+{
+	if ( !m_tag_map ) return transform();
+	NV_ASSERT( node_id < m_tag_map->size(), "TAGMAP FAIL" );
+	NV_ASSERT( (*m_tag_map)[node_id]->size() > 0, "TAG FAIL" );
+	raw_channel_interpolator interpolator( ( *m_tag_map )[node_id], m_interpolation_key );
+	return interpolator.get< transform >( m_last_frame, m_next_frame, m_interpolation );
+}
+
+mat4 keyframed_mesh::get_node_matrix( uint32 node_id ) const
+{
+	return get_node_transform( node_id ).extract();
+}
+
+void nv::keyframed_mesh::set_frame( uint32 frame )
+{
+	m_last_frame    = frame;
+	m_next_frame    = frame;
+	m_active        = false;
+	m_interpolation = 0.0f;
+}
+
+void nv::keyframed_mesh::update_animation( animation_entry* anim, uint32 a_ms_anim_time )
+{
+	if ( m_active )
+	{
+		float  fframe   = ( static_cast<float>( a_ms_anim_time ) * 0.001f ) * anim->get_fps();
+		uint32 frame    = uint32( fframe );
+		float  reminder = fframe - static_cast<float>( frame );
+		uint32 duration = anim->is_looping() ? anim->get_frame_count() + 1 : anim->get_frame_count();
+
+		if ( frame >= duration )
+		{
+			if ( anim->is_looping() )
+			{
+				frame  = frame % duration;
+				fframe = static_cast<float>( frame ) + reminder;
+			}
+			else
+			{
+				m_active        = false;
+				m_last_frame    = anim->get_end_frame();
+				m_next_frame    = m_last_frame;
+				m_interpolation = 0.0f;
+				return;
+			}
+		}
+		m_last_frame    = frame + anim->get_start_frame();
+		m_next_frame    = m_last_frame + 1;
+		if ( m_next_frame > anim->get_end_frame() ) m_next_frame = anim->get_start_frame();
+		m_interpolation = reminder;
+	}
+}
+
+
+void nv::keyframed_mesh::update( program a_program )
+{
+	m_context->get_device()->set_opt_uniform( a_program, "nv_interpolate", m_interpolation );
+}
+
+nv::keyframed_mesh::~keyframed_mesh()
+{
+	m_context->release( m_va );
+}
+
+void nv::keyframed_mesh::run_animation( animation_entry* a_anim )
+{
+	if ( a_anim )
+	{
+		m_active        = true;
+		m_last_frame    = 0;
+		m_next_frame    = 0;
+		m_interpolation = 0.0f;
+	}
+	else
+	{
+		m_active = false;
+	}
+}
+
+nv::keyframed_mesh_gpu::keyframed_mesh_gpu( context* a_context, const data_channel_set* a_data, const mesh_nodes_data* a_tag_map )
+	: keyframed_mesh( a_context, a_data, a_tag_map )
+	, m_loc_next_position( -1 )
+	, m_loc_next_normal( -1 )
+	, m_loc_next_tangent( -1 )
+	, m_gpu_last_frame( 0xFFFFFFFF )
+	, m_gpu_next_frame( 0xFFFFFFFF )
+{
+	m_va      = a_context->create_vertex_array( a_data, STATIC_DRAW );
+	m_pbuffer = a_context->find_buffer( m_va, slot::POSITION );
+}
+
+
+void nv::keyframed_mesh_gpu::update_animation( animation_entry* anim, uint32 a_anim_time )
+{
+	keyframed_mesh::update_animation( anim, a_anim_time );
+	if ( m_loc_next_position == -1 ) return;
+	if ( m_gpu_last_frame != m_last_frame )
+	{
+		uint32 base_offset = m_last_frame * m_vertex_count * m_vsize; 
+		m_context->update_attribute_offset( m_va, slot::POSITION, base_offset );
+		m_context->update_attribute_offset( m_va, slot::NORMAL,   base_offset + sizeof( vec3 ) );
+		if ( m_has_tangent && m_loc_next_tangent != -1 )
+		{
+			m_context->update_attribute_offset( m_va, slot::TANGENT, base_offset + 2*sizeof( vec3 ) );
+		}
+		m_gpu_last_frame = m_last_frame;
+	}
+	if ( m_loc_next_position != -1 && m_gpu_next_frame != m_next_frame )
+	{
+		uint32 base_offset = m_next_frame * m_vertex_count * m_vsize; 
+		m_context->update_attribute_offset( m_va, static_cast<slot>( m_loc_next_position ), base_offset );
+		m_context->update_attribute_offset( m_va, static_cast<slot>( m_loc_next_normal ), base_offset + sizeof( vec3 ) );
+		if ( m_has_tangent && m_loc_next_tangent != -1 )
+		{
+			m_context->update_attribute_offset( m_va, static_cast<slot>( m_loc_next_tangent ), base_offset + 2*sizeof( vec3 ) );
+		}
+		m_gpu_next_frame = m_next_frame;
+	}
+}
+
+void nv::keyframed_mesh_gpu::update( program a_program )
+{
+	if ( m_loc_next_position == -1 )
+	{
+		device* dev = m_context->get_device();
+		m_loc_next_position = dev->get_attribute_location( a_program, "nv_next_position" );
+		m_loc_next_normal   = dev->get_attribute_location( a_program, "nv_next_normal" );
+		if ( m_has_tangent )
+			m_loc_next_tangent  = dev->get_attribute_location( a_program, "nv_next_tangent" );
+
+		m_context->add_vertex_buffer( m_va, static_cast<slot>( m_loc_next_position ), m_pbuffer, FLOAT, 3, 0, m_vsize, false );
+		m_context->add_vertex_buffer( m_va, static_cast<slot>( m_loc_next_normal ),   m_pbuffer, FLOAT, 3, sizeof( vec3 ), m_vsize, false );
+		if ( m_has_tangent )
+			m_context->add_vertex_buffer( m_va, static_cast<slot>( m_loc_next_tangent ), m_pbuffer, FLOAT, 4, 2*sizeof( vec3 ), m_vsize, false );
+	}
+	keyframed_mesh::update( a_program );
+}
+
+nv::keyframed_mesh_cpu::keyframed_mesh_cpu( context* a_context, const data_channel_set* a_data, const mesh_nodes_data* a_tag_map )
+	: keyframed_mesh( a_context, a_data, a_tag_map )
+{
+	const raw_data_channel* vchannel = m_has_tangent ? a_data->get_channel< vertex_pnt >() : a_data->get_channel< vertex_pn >();
+	m_va      = m_context->create_vertex_array();
+	m_pbuffer = m_context->get_device()->create_buffer( VERTEX_BUFFER, STATIC_DRAW, m_vertex_count * m_vsize, vchannel->raw_data() );
+	m_context->add_vertex_buffers( m_va, m_pbuffer, vchannel->descriptor() );
+
+	buffer  vb = m_context->get_device()->create_buffer( VERTEX_BUFFER, STATIC_DRAW, m_vertex_count * sizeof( vec2 ), a_data->get_channel_data<vertex_t>() );
+	m_context->add_vertex_buffers( m_va, vb, a_data->get_channel<vertex_t>()->descriptor() );
+
+	const raw_data_channel* index_channel = a_data->get_channel( slot::INDEX );
+	buffer  ib = m_context->get_device()->create_buffer( INDEX_BUFFER, STATIC_DRAW, index_channel->raw_size(), index_channel->raw_data() );
+	m_context->set_index_buffer( m_va, ib, index_channel->descriptor()[0].etype, true );
+
+	m_data = new uint8[ m_vertex_count * m_vsize ];
+
+	m_model_data = vchannel->raw_data();
+}
+
+void nv::keyframed_mesh_cpu::update_animation( animation_entry* anim, uint32 a_anim_time )
+{
+	keyframed_mesh::update_animation( anim, a_anim_time );
+	// TODO: this could be done generic for any data
+	if ( m_has_tangent )
+	{
+		const vertex_pnt* data = reinterpret_cast< const vertex_pnt* >( m_model_data );
+		const vertex_pnt* prev = data + m_vertex_count * m_last_frame;
+		const vertex_pnt* next = data + m_vertex_count * m_next_frame;
+		      vertex_pnt* vtx  = reinterpret_cast<vertex_pnt*>( m_data );
+		for ( size_t i = 0; i < m_vertex_count; ++i )
+		{
+			vtx[i].position = math::mix( prev[i].position, next[i].position, m_interpolation );
+			vtx[i].normal   = math::mix( prev[i].normal,   next[i].normal,   m_interpolation );
+			vtx[i].tangent  = math::mix( prev[i].tangent,  next[i].tangent,   m_interpolation );
+		}
+	}
+	else
+	{
+		const vertex_pn* data = reinterpret_cast< const vertex_pn* >( m_model_data );
+		const vertex_pn* prev = data + m_vertex_count * m_last_frame;
+		const vertex_pn* next = data + m_vertex_count * m_next_frame;
+		      vertex_pn* vtx  = reinterpret_cast<vertex_pn*>( m_data );
+
+		for ( size_t i = 0; i < m_vertex_count; ++i )
+		{
+			vtx[i].position = math::mix( prev[i].position, next[i].position, m_interpolation );
+			vtx[i].normal   = math::mix( prev[i].normal,   next[i].normal,   m_interpolation );
+		}
+	}
+
+	m_context->update( m_pbuffer, m_data, 0, m_vertex_count * m_vsize );
+}
+
+nv::keyframed_mesh_cpu::~keyframed_mesh_cpu()
+{
+	delete[] m_data;
+}
+
+#endif 
Index: /trunk/legacy/keyframed_mesh.hh
===================================================================
--- /trunk/legacy/keyframed_mesh.hh	(revision 480)
+++ /trunk/legacy/keyframed_mesh.hh	(revision 480)
@@ -0,0 +1,102 @@
+// Copyright (C) 2012-2015 ChaosForge Ltd
+// http://chaosforge.org/
+//
+// This file is part of Nova libraries. 
+// For conditions of distribution and use, see copying.txt file in root folder.
+
+#ifndef NV_GFX_KEYFRAMED_MESH_HH
+#define NV_GFX_KEYFRAMED_MESH_HH
+
+#include <nv/common.hh>
+#include <nv/interface/context.hh>
+#include <nv/interface/animated_mesh.hh>
+
+namespace nv
+{
+
+#if 0
+
+	class keyframed_mesh : public animated_mesh
+	{
+	public:
+		keyframed_mesh( context* a_context, const data_channel_set* a_data, const mesh_nodes_data* a_tag_map );
+		virtual vertex_array get_vertex_array() const { return m_va; }
+		virtual size_t get_index_count() const { return m_index_count; }
+		virtual void run_animation( animation_entry* a_anim );
+		size_t get_max_frames() const;
+		virtual transform get_node_transform( uint32 node_id ) const;
+		virtual mat4 get_node_matrix( uint32 node_id ) const;
+		virtual void update_animation( animation_entry*, uint32 a_ms_anim_time );
+		virtual void update( program a_program );
+		virtual ~keyframed_mesh();
+	protected:
+		virtual void set_frame( uint32 frame );
+
+		struct vertex_pn
+		{
+			vec3 position;
+			vec3 normal;
+		};
+		struct vertex_pnt
+		{
+			vec3 position;
+			vec3 normal;
+			vec4 tangent;
+		};
+		struct vertex_t
+		{
+			vec2 texcoord;
+		};
+
+		context*     m_context;
+
+		const mesh_nodes_data*  m_tag_map;
+
+		data_descriptor m_interpolation_key;
+		buffer       m_pbuffer;
+		vertex_array m_va;
+
+		uint32 m_last_frame;
+		uint32 m_next_frame;
+		uint32 m_vsize;
+		f32    m_interpolation;
+		bool   m_has_tangent;
+		bool   m_active;
+
+		uint32 m_index_count;
+		uint32 m_frame_count;
+		uint32 m_vertex_count;
+	};
+
+
+	class keyframed_mesh_gpu : public keyframed_mesh
+	{
+	public:
+		keyframed_mesh_gpu( context* a_context, const data_channel_set* a_data, const mesh_nodes_data* a_tag_map );
+		void update_animation( animation_entry* anim, uint32 a_anim_time );
+		virtual void update( program a_program );
+	private:
+		int m_loc_next_position;
+		int m_loc_next_normal;
+		int m_loc_next_tangent;
+
+		uint32 m_gpu_last_frame;
+		uint32 m_gpu_next_frame;
+	};
+
+	class keyframed_mesh_cpu : public keyframed_mesh
+	{
+	public:
+		keyframed_mesh_cpu( context* a_context, const data_channel_set* a_data, const mesh_nodes_data* a_tag_map );
+		void update_animation( animation_entry* anim, uint32 a_anim_time );
+		~keyframed_mesh_cpu();
+	private:
+		const uint8* m_model_data;
+		uint8*  m_data;
+	};
+
+#endif
+
+} // namespace nv
+
+#endif // NV_GFX_KEYFRAMED_MESH_HH
Index: /trunk/legacy/md2_loader.cc
===================================================================
--- /trunk/legacy/md2_loader.cc	(revision 480)
+++ /trunk/legacy/md2_loader.cc	(revision 480)
@@ -0,0 +1,369 @@
+// Copyright (C) 2012-2015 ChaosForge Ltd
+// http://chaosforge.org/
+//
+// This file is part of Nova libraries. 
+// For conditions of distribution and use, see copying.txt file in root folder.
+
+#include "nv/formats/md2_loader.hh"
+
+#include "nv/core/logging.hh"
+#include "nv/interface/data_channel_access.hh"
+
+#include <cstring>
+
+using namespace nv;
+
+// based on http://tfc.duke.free.fr/coding/md2-specs-en.html
+
+// assuming low-endian
+#define MD2_MAX_FRAMES    512
+#define MD2_MAX_SKINS     32
+#define MD2_MAX_VERTS     2048
+#define MD2_MAX_TRIANGLES 4096
+#define MD2_MAX_TEXCOORDS 2048
+#define MD2_NORMAL_COUNT  162
+
+typedef float md2_vec3_t[3];
+
+static const md2_vec3_t md2_normal_table[MD2_NORMAL_COUNT] = {
+#include "src/formats/md2_normals.inc"
+};
+
+struct md2_header_t
+{
+	char magic[4];
+	int version;
+
+	int skinwidth;
+	int skinheight;
+
+	int framesize;
+
+	int num_skins;
+	int num_vertices;
+	int num_st;
+	int num_tris;
+	int num_glcmds;
+	int num_frames;
+
+	int offset_skins;
+	int offset_st;
+	int offset_tris;
+	int offset_frames;
+	int offset_glcmds;
+	int offset_end;
+};
+
+struct md2_skin_t
+{
+	char name[64];
+};
+
+struct md2_texcoord_t
+{
+	short s;
+	short t;
+};
+
+struct md2_triangle_t
+{
+	unsigned short vertex[3];
+	unsigned short st[3];
+};
+
+struct md2_vertex_t
+{
+	uint8 v[3];
+	uint8 n;
+};
+
+struct md2_frame_t
+{
+	md2_vec3_t scale;
+	md2_vec3_t translate;
+	char name[16];
+	md2_vertex_t *vertices;
+};
+
+struct md2_glcmd_t
+{
+	float s;
+	float t;
+	int index;
+};
+
+struct md2_t
+{
+	md2_header_t    header;
+	md2_skin_t*     skins;
+	md2_texcoord_t* texcoords;
+	md2_triangle_t* triangles;
+	md2_frame_t*    frames;
+	int*            glcmds;
+};
+
+static bool check_md2_magic( char* magic )
+{
+	return magic[0] == 'I' && magic[1] == 'D' && magic[2] == 'P' && magic[3] == '2';
+}
+
+static void free_md2_frame( md2_frame_t* frame )
+{
+	delete[] frame->vertices;
+}
+
+static void free_md2( md2_t* md2 )
+{
+	delete[] md2->skins;
+	delete[] md2->texcoords;
+	delete[] md2->triangles;
+	delete[] md2->glcmds;
+	for ( int i = 0; i < md2->header.num_frames; ++i )
+	{
+		free_md2_frame( &(md2->frames[i]) );
+	}
+	delete[] md2->frames;
+}
+
+static bool read_md2_frame( md2_frame_t* frame, unsigned vcount, nv::stream& source )
+{
+	frame->vertices = new md2_vertex_t[ vcount ];
+	source.read( frame->scale,     sizeof(md2_vec3_t), 1 );
+	source.read( frame->translate, sizeof(md2_vec3_t), 1 );
+	source.read( frame->name,      sizeof(char), 16 );
+	source.read( frame->vertices,  sizeof(md2_vertex_t), vcount );
+	return true;
+}
+
+static bool read_md2( md2_t* md2, nv::stream& source )
+{
+	md2->frames     = nullptr;
+	md2->skins      = nullptr;
+	md2->texcoords  = nullptr;
+	md2->triangles  = nullptr;
+	md2->glcmds     = nullptr;
+
+	source.read( &(md2->header), sizeof(md2_header_t), 1 );
+
+	if ( !check_md2_magic( md2->header.magic )       ||
+		md2->header.num_skins    > MD2_MAX_SKINS     ||
+		md2->header.num_vertices > MD2_MAX_VERTS     ||
+		md2->header.num_st       > MD2_MAX_TEXCOORDS ||
+		md2->header.num_tris     > MD2_MAX_TRIANGLES ||
+		md2->header.num_frames   > MD2_MAX_FRAMES )
+	{
+		return false;
+	}
+
+	NV_LOG_INFO( "num_skins    = ", md2->header.num_skins );
+	NV_LOG_INFO( "num_vertices = ", md2->header.num_vertices );
+	NV_LOG_INFO( "num_st       = ", md2->header.num_st );
+	NV_LOG_INFO( "num_tris     = ", md2->header.num_tris );
+	NV_LOG_INFO( "num_frames   = ", md2->header.num_frames );
+
+
+	md2->skins      = new md2_skin_t    [ md2->header.num_skins ];
+	md2->texcoords  = new md2_texcoord_t[ md2->header.num_st ];
+	md2->triangles  = new md2_triangle_t[ md2->header.num_tris ];
+	md2->glcmds     = new int           [ md2->header.num_glcmds ];
+	
+	source.seek( md2->header.offset_skins, origin::SET );
+	source.read( md2->skins, sizeof( md2_skin_t ), static_cast<nv::size_t>( md2->header.num_skins ) );
+
+	source.seek( md2->header.offset_st, origin::SET );
+	source.read( md2->texcoords, sizeof( md2_texcoord_t ), static_cast<nv::size_t>( md2->header.num_st ) );
+
+	source.seek( md2->header.offset_tris, origin::SET );
+	source.read( md2->triangles, sizeof( md2_triangle_t ), static_cast<nv::size_t>( md2->header.num_tris ) );
+
+	source.seek( md2->header.offset_glcmds, origin::SET);
+	source.read( md2->glcmds, sizeof( int ), static_cast<nv::size_t>( md2->header.num_glcmds ) );
+
+	md2->frames    = new md2_frame_t   [ md2->header.num_frames ];
+	source.seek( md2->header.offset_frames, origin::SET );
+	for ( int i = 0; i < md2->header.num_frames; ++i )
+	{
+		if (!read_md2_frame( &(md2->frames[i]), static_cast<unsigned>( md2->header.num_vertices ), source ) ) return false;
+	}
+
+	return true;
+}
+
+static inline vec3 md2_vec3( const md2_vec3_t& v )
+{
+	//	return vec3( v[0], v[1], v[2] );
+	return vec3( v[0], v[2], v[1] );
+}
+
+static vec3 s_md2_normal_cache[MD2_NORMAL_COUNT];
+static bool s_md2_normal_ready = false;
+
+md2_loader::md2_loader( string_table* strings ) 
+	: mesh_loader( strings ), m_md2( nullptr )
+{
+	if ( !s_md2_normal_ready )
+	{
+		for ( int i = 0; i < MD2_NORMAL_COUNT; ++i )
+		{
+			s_md2_normal_cache[i].x = md2_normal_table[i][0];
+//			s_md2_normal_cache[i].y = md2_normal_table[i][1];
+//			s_md2_normal_cache[i].z = md2_normal_table[i][2];
+			s_md2_normal_cache[i].y = md2_normal_table[i][2];
+			s_md2_normal_cache[i].z = md2_normal_table[i][1];
+		}
+	}
+}
+
+
+md2_loader::~md2_loader()
+{
+	md2_t* md2 = reinterpret_cast< md2_t* >( m_md2 );
+	if ( md2 != nullptr)
+	{
+		free_md2( md2 );
+		delete md2;
+	}
+}
+
+bool md2_loader::load( stream& source )
+{
+	md2_t* md2 = new md2_t; 
+	m_md2 = md2;
+	if ( !read_md2( md2, source ) )
+	{
+		return false;
+	}
+	reindex();
+	return true;
+}
+
+nv::size_t md2_loader::get_max_frames() const
+{
+	return static_cast<size_t>( reinterpret_cast<md2_t*>( m_md2 )->header.num_frames );
+}
+
+void nv::md2_loader::reindex()
+{
+	md2_t* md2 = reinterpret_cast< md2_t* >( m_md2 );
+	uint32 num_indexes = static_cast< uint32 >( md2->header.num_tris * 3 );
+
+	uint32 stats_reuse      = 0;
+	uint32 stats_collision  = 0;
+
+	vector< sint32 > index_translation( static_cast< uint32 >( md2->header.num_vertices ), -1 );
+
+	m_new_indexes.clear();
+	m_new_indexes.reserve( num_indexes );
+	m_new_vindexes.reserve( num_indexes );
+	m_new_tindexes.reserve( num_indexes );
+
+	uint16 index_count = 0;
+
+	for ( int i = 0; i < md2->header.num_tris; ++i )
+	{
+		const md2_triangle_t& t = md2->triangles[i];
+		for ( int j = 0; j < 3; ++j )
+		{
+			uint16 index  = t.vertex[j];
+			uint16 tindex = t.st[j];
+
+			if ( index_translation[ index ] != -1 )
+			{
+				uint16 prev = static_cast< uint16 >( index_translation[ index ] );
+				if ( m_new_tindexes[ prev ] == tindex )
+				{
+					m_new_indexes.push_back( prev );
+					stats_reuse++;
+					continue;
+				}
+			}
+			
+			m_new_vindexes.push_back( index );
+			m_new_tindexes.push_back( tindex );
+			m_new_indexes.push_back( index_count );
+			if ( index_translation[ index ] == -1 )
+			{
+				index_translation[ index ] = index_count;
+			}
+			else
+			{
+				stats_collision++;
+			}
+			index_count++;
+		}
+	}
+
+	NV_LOG_INFO( "New vertex count = ", m_new_vindexes.size() );
+	NV_LOG_INFO( "Collisions       = ", stats_collision );
+	NV_LOG_INFO( "Reuse count      = ", stats_reuse );
+}
+
+
+struct vtx_md2_pn
+{
+	nv::vec3 position;
+	nv::vec3 normal;
+};
+
+struct vtx_md2_t
+{
+	nv::vec2 texcoord;
+};
+
+
+data_channel_set* nv::md2_loader::release_mesh_data( size_t )
+{
+	data_channel_set* data = data_channel_set_creator::create_set( 3 );
+	release_mesh_frame( data, -1 );
+	return data;
+}
+
+void nv::md2_loader::release_mesh_frame( data_channel_set* data, sint32 frame )
+{
+	md2_t* md2 = reinterpret_cast< md2_t* >( m_md2 );
+	size_t num_frames = static_cast< size_t >( md2->header.num_frames );
+	size_t num_verts  =	m_new_vindexes.size();
+	size_t current_frame = ( frame == -1 ? 0 : static_cast< size_t >( frame ) );
+	size_t frame_count   = ( frame == -1 ? num_frames : 1 );
+
+	data_channel_set_creator maccess( data );
+	vtx_md2_pn* vtx_pn = maccess.add_channel< vtx_md2_pn >( num_verts * frame_count ).data();
+	vtx_md2_t* vtx_t   = maccess.add_channel< vtx_md2_t >( num_verts ).data();
+	uint16* icp        = &maccess.add_channel< index_u16 >( m_new_indexes.size() ).data()->index;
+
+	maccess.set_name( make_name( "md2_mesh" ) );
+
+	uint32 index = 0;
+	while ( frame_count > 0 )
+	{
+		const md2_frame_t& cframe = md2->frames[current_frame];
+		NV_LOG_INFO( "FrameID = ", cframe.name );
+
+		vec3 scale     = md2_vec3( cframe.scale );
+		vec3 translate = md2_vec3( cframe.translate );
+
+		for (size_t i = 0; i < num_verts; ++i )
+		{
+			const md2_vertex_t& v = cframe.vertices[ m_new_vindexes[ i ] ];
+			vtx_pn[index].position = vec3( v.v[0], v.v[2], v.v[1] ) * scale + translate;
+			vtx_pn[index].normal   = s_md2_normal_cache[ v.n ];
+			index++;
+		}
+		++current_frame;
+		--frame_count;
+	}
+
+	vec2 scale( 1.0f / static_cast<float>( md2->header.skinwidth ), 1.0f / static_cast<float>( md2->header.skinheight ) );
+	for (size_t i = 0; i < num_verts; ++i )
+	{
+		const md2_texcoord_t& st = md2->texcoords[ m_new_tindexes[ i ] ];
+		vtx_t[i].texcoord = scale * vec2( st.s, st.t );
+	}
+
+	if ( m_new_indexes.size() > 0 )
+	{
+		raw_copy_n( m_new_indexes.data(), m_new_indexes.size(), icp );
+	}
+
+}
+
Index: /trunk/legacy/md2_loader.hh
===================================================================
--- /trunk/legacy/md2_loader.hh	(revision 480)
+++ /trunk/legacy/md2_loader.hh	(revision 480)
@@ -0,0 +1,45 @@
+// Copyright (C) 2012-2015 ChaosForge Ltd
+// http://chaosforge.org/
+//
+// This file is part of Nova libraries. 
+// For conditions of distribution and use, see copying.txt file in root folder.
+
+/**
+ * @file md2_loader.hh
+ * @author Kornel Kisielewicz
+ * @brief md2 loader
+ */
+
+#ifndef NV_FORMATS_MD2_LOADER_HH
+#define NV_FORMATS_MD2_LOADER_HH
+
+#include <nv/common.hh>
+#include <nv/stl/vector.hh>
+#include <nv/interface/mesh_loader.hh>
+
+namespace nv 
+{
+
+	class md2_loader : public mesh_loader
+	{
+	public:
+		explicit md2_loader( string_table* strings );
+		virtual ~md2_loader();
+		virtual bool load( stream& source );
+		virtual data_channel_set* release_mesh_data( size_t index = 0 );
+		virtual size_t get_mesh_count() const { return 1; }
+	private:
+		void release_mesh_frame( data_channel_set* data, sint32 frame );
+		size_t get_max_frames() const;
+		void reindex();
+	private:
+		void* m_md2;
+		string_table* m_strings;
+		vector< uint16 > m_new_indexes;
+		vector< uint16 > m_new_vindexes;
+		vector< uint16 > m_new_tindexes;
+	};
+
+}
+
+#endif // NV_FORMATS_MD2_LOADER_HH
Index: /trunk/legacy/md2_normals.inc
===================================================================
--- /trunk/legacy/md2_normals.inc	(revision 480)
+++ /trunk/legacy/md2_normals.inc	(revision 480)
@@ -0,0 +1,162 @@
+{ -0.525731f,  0.000000f,  0.850651f }, 
+{ -0.442863f,  0.238856f,  0.864188f }, 
+{ -0.295242f,  0.000000f,  0.955423f }, 
+{ -0.309017f,  0.500000f,  0.809017f }, 
+{ -0.162460f,  0.262866f,  0.951056f }, 
+{  0.000000f,  0.000000f,  1.000000f }, 
+{  0.000000f,  0.850651f,  0.525731f }, 
+{ -0.147621f,  0.716567f,  0.681718f }, 
+{  0.147621f,  0.716567f,  0.681718f }, 
+{  0.000000f,  0.525731f,  0.850651f }, 
+{  0.309017f,  0.500000f,  0.809017f }, 
+{  0.525731f,  0.000000f,  0.850651f }, 
+{  0.295242f,  0.000000f,  0.955423f }, 
+{  0.442863f,  0.238856f,  0.864188f }, 
+{  0.162460f,  0.262866f,  0.951056f }, 
+{ -0.681718f,  0.147621f,  0.716567f }, 
+{ -0.809017f,  0.309017f,  0.500000f }, 
+{ -0.587785f,  0.425325f,  0.688191f }, 
+{ -0.850651f,  0.525731f,  0.000000f }, 
+{ -0.864188f,  0.442863f,  0.238856f }, 
+{ -0.716567f,  0.681718f,  0.147621f }, 
+{ -0.688191f,  0.587785f,  0.425325f }, 
+{ -0.500000f,  0.809017f,  0.309017f }, 
+{ -0.238856f,  0.864188f,  0.442863f }, 
+{ -0.425325f,  0.688191f,  0.587785f }, 
+{ -0.716567f,  0.681718f, -0.147621f }, 
+{ -0.500000f,  0.809017f, -0.309017f }, 
+{ -0.525731f,  0.850651f,  0.000000f }, 
+{  0.000000f,  0.850651f, -0.525731f }, 
+{ -0.238856f,  0.864188f, -0.442863f }, 
+{  0.000000f,  0.955423f, -0.295242f }, 
+{ -0.262866f,  0.951056f, -0.162460f }, 
+{  0.000000f,  1.000000f,  0.000000f }, 
+{  0.000000f,  0.955423f,  0.295242f }, 
+{ -0.262866f,  0.951056f,  0.162460f }, 
+{  0.238856f,  0.864188f,  0.442863f }, 
+{  0.262866f,  0.951056f,  0.162460f }, 
+{  0.500000f,  0.809017f,  0.309017f }, 
+{  0.238856f,  0.864188f, -0.442863f }, 
+{  0.262866f,  0.951056f, -0.162460f }, 
+{  0.500000f,  0.809017f, -0.309017f }, 
+{  0.850651f,  0.525731f,  0.000000f }, 
+{  0.716567f,  0.681718f,  0.147621f }, 
+{  0.716567f,  0.681718f, -0.147621f }, 
+{  0.525731f,  0.850651f,  0.000000f }, 
+{  0.425325f,  0.688191f,  0.587785f }, 
+{  0.864188f,  0.442863f,  0.238856f }, 
+{  0.688191f,  0.587785f,  0.425325f }, 
+{  0.809017f,  0.309017f,  0.500000f }, 
+{  0.681718f,  0.147621f,  0.716567f }, 
+{  0.587785f,  0.425325f,  0.688191f }, 
+{  0.955423f,  0.295242f,  0.000000f }, 
+{  1.000000f,  0.000000f,  0.000000f }, 
+{  0.951056f,  0.162460f,  0.262866f }, 
+{  0.850651f, -0.525731f,  0.000000f }, 
+{  0.955423f, -0.295242f,  0.000000f }, 
+{  0.864188f, -0.442863f,  0.238856f }, 
+{  0.951056f, -0.162460f,  0.262866f }, 
+{  0.809017f, -0.309017f,  0.500000f }, 
+{  0.681718f, -0.147621f,  0.716567f }, 
+{  0.850651f,  0.000000f,  0.525731f }, 
+{  0.864188f,  0.442863f, -0.238856f }, 
+{  0.809017f,  0.309017f, -0.500000f }, 
+{  0.951056f,  0.162460f, -0.262866f }, 
+{  0.525731f,  0.000000f, -0.850651f }, 
+{  0.681718f,  0.147621f, -0.716567f }, 
+{  0.681718f, -0.147621f, -0.716567f }, 
+{  0.850651f,  0.000000f, -0.525731f }, 
+{  0.809017f, -0.309017f, -0.500000f }, 
+{  0.864188f, -0.442863f, -0.238856f }, 
+{  0.951056f, -0.162460f, -0.262866f }, 
+{  0.147621f,  0.716567f, -0.681718f }, 
+{  0.309017f,  0.500000f, -0.809017f }, 
+{  0.425325f,  0.688191f, -0.587785f }, 
+{  0.442863f,  0.238856f, -0.864188f }, 
+{  0.587785f,  0.425325f, -0.688191f }, 
+{  0.688191f,  0.587785f, -0.425325f }, 
+{ -0.147621f,  0.716567f, -0.681718f }, 
+{ -0.309017f,  0.500000f, -0.809017f }, 
+{  0.000000f,  0.525731f, -0.850651f }, 
+{ -0.525731f,  0.000000f, -0.850651f }, 
+{ -0.442863f,  0.238856f, -0.864188f }, 
+{ -0.295242f,  0.000000f, -0.955423f }, 
+{ -0.162460f,  0.262866f, -0.951056f }, 
+{  0.000000f,  0.000000f, -1.000000f }, 
+{  0.295242f,  0.000000f, -0.955423f }, 
+{  0.162460f,  0.262866f, -0.951056f }, 
+{ -0.442863f, -0.238856f, -0.864188f }, 
+{ -0.309017f, -0.500000f, -0.809017f }, 
+{ -0.162460f, -0.262866f, -0.951056f }, 
+{  0.000000f, -0.850651f, -0.525731f }, 
+{ -0.147621f, -0.716567f, -0.681718f }, 
+{  0.147621f, -0.716567f, -0.681718f }, 
+{  0.000000f, -0.525731f, -0.850651f }, 
+{  0.309017f, -0.500000f, -0.809017f }, 
+{  0.442863f, -0.238856f, -0.864188f }, 
+{  0.162460f, -0.262866f, -0.951056f }, 
+{  0.238856f, -0.864188f, -0.442863f }, 
+{  0.500000f, -0.809017f, -0.309017f }, 
+{  0.425325f, -0.688191f, -0.587785f }, 
+{  0.716567f, -0.681718f, -0.147621f }, 
+{  0.688191f, -0.587785f, -0.425325f }, 
+{  0.587785f, -0.425325f, -0.688191f }, 
+{  0.000000f, -0.955423f, -0.295242f }, 
+{  0.000000f, -1.000000f,  0.000000f }, 
+{  0.262866f, -0.951056f, -0.162460f }, 
+{  0.000000f, -0.850651f,  0.525731f }, 
+{  0.000000f, -0.955423f,  0.295242f }, 
+{  0.238856f, -0.864188f,  0.442863f }, 
+{  0.262866f, -0.951056f,  0.162460f }, 
+{  0.500000f, -0.809017f,  0.309017f }, 
+{  0.716567f, -0.681718f,  0.147621f }, 
+{  0.525731f, -0.850651f,  0.000000f }, 
+{ -0.238856f, -0.864188f, -0.442863f }, 
+{ -0.500000f, -0.809017f, -0.309017f }, 
+{ -0.262866f, -0.951056f, -0.162460f }, 
+{ -0.850651f, -0.525731f,  0.000000f }, 
+{ -0.716567f, -0.681718f, -0.147621f }, 
+{ -0.716567f, -0.681718f,  0.147621f }, 
+{ -0.525731f, -0.850651f,  0.000000f }, 
+{ -0.500000f, -0.809017f,  0.309017f }, 
+{ -0.238856f, -0.864188f,  0.442863f }, 
+{ -0.262866f, -0.951056f,  0.162460f }, 
+{ -0.864188f, -0.442863f,  0.238856f }, 
+{ -0.809017f, -0.309017f,  0.500000f }, 
+{ -0.688191f, -0.587785f,  0.425325f }, 
+{ -0.681718f, -0.147621f,  0.716567f }, 
+{ -0.442863f, -0.238856f,  0.864188f }, 
+{ -0.587785f, -0.425325f,  0.688191f }, 
+{ -0.309017f, -0.500000f,  0.809017f }, 
+{ -0.147621f, -0.716567f,  0.681718f }, 
+{ -0.425325f, -0.688191f,  0.587785f }, 
+{ -0.162460f, -0.262866f,  0.951056f }, 
+{  0.442863f, -0.238856f,  0.864188f }, 
+{  0.162460f, -0.262866f,  0.951056f }, 
+{  0.309017f, -0.500000f,  0.809017f }, 
+{  0.147621f, -0.716567f,  0.681718f }, 
+{  0.000000f, -0.525731f,  0.850651f }, 
+{  0.425325f, -0.688191f,  0.587785f }, 
+{  0.587785f, -0.425325f,  0.688191f }, 
+{  0.688191f, -0.587785f,  0.425325f }, 
+{ -0.955423f,  0.295242f,  0.000000f }, 
+{ -0.951056f,  0.162460f,  0.262866f }, 
+{ -1.000000f,  0.000000f,  0.000000f }, 
+{ -0.850651f,  0.000000f,  0.525731f }, 
+{ -0.955423f, -0.295242f,  0.000000f }, 
+{ -0.951056f, -0.162460f,  0.262866f }, 
+{ -0.864188f,  0.442863f, -0.238856f }, 
+{ -0.951056f,  0.162460f, -0.262866f }, 
+{ -0.809017f,  0.309017f, -0.500000f }, 
+{ -0.864188f, -0.442863f, -0.238856f }, 
+{ -0.951056f, -0.162460f, -0.262866f }, 
+{ -0.809017f, -0.309017f, -0.500000f }, 
+{ -0.681718f,  0.147621f, -0.716567f }, 
+{ -0.681718f, -0.147621f, -0.716567f }, 
+{ -0.850651f,  0.000000f, -0.525731f }, 
+{ -0.688191f,  0.587785f, -0.425325f }, 
+{ -0.587785f,  0.425325f, -0.688191f }, 
+{ -0.425325f,  0.688191f, -0.587785f }, 
+{ -0.425325f, -0.688191f, -0.587785f }, 
+{ -0.587785f, -0.425325f, -0.688191f }, 
+{ -0.688191f, -0.587785f, -0.425325f }
Index: /trunk/legacy/md3_loader.cc
===================================================================
--- /trunk/legacy/md3_loader.cc	(revision 480)
+++ /trunk/legacy/md3_loader.cc	(revision 480)
@@ -0,0 +1,440 @@
+// Copyright (C) 2012-2015 ChaosForge Ltd
+// http://chaosforge.org/
+//
+// This file is part of Nova libraries. 
+// For conditions of distribution and use, see copying.txt file in root folder.
+
+#include "nv/formats/md3_loader.hh"
+
+#include "nv/core/logging.hh"
+#include "nv/interface/data_channel_access.hh"
+
+using namespace nv;
+
+// based on http://www.icculus.org/~phaethon/q3/formats/md3format.html#Surface
+
+// assuming low-endian
+#define MD3_MAX_FRAMES    1024
+#define MD3_MAX_TAGS      16
+#define MD3_MAX_SURFACES  32
+#define MD3_MAX_SHADERS   256
+#define MD3_MAX_VERTS     4096
+#define MD3_MAX_TRIANGLES 8192
+#define MD3_XYZ_SCALE     (1.0f/64.0f)
+
+struct md3_vec3_t
+{
+	float xyz[3];
+};
+
+struct md3_header_t
+{
+	char   ident[4]; // IDP3
+	sint32 version;  // 15
+	uint8  name[64]; // path name
+	sint32 flags;
+	sint32 num_frames;     // Number of Frame objects, with a maximum of MD3_MAX_FRAMES. Current value of MD3_MAX_FRAMES is 1024.
+	sint32 num_tags;       // Number of Tag objects, with a maximum of MD3_MAX_TAGS. Current value of MD3_MAX_TAGS is 16.
+	sint32 num_surfaces;   // Number of Surface objects, with a maximum of MD3_MAX_SURFACES. Current value of MD3_MAX_SURFACES is 32.
+	sint32 num_skins;      // Number of Skin objects. I should note that I have not seen an MD3 using this particular field for anything; this appears to be an artifact from the Quake 2 MD2 format. Surface objects have their own Shader field.
+	sint32 ofs_frames;     // Relative offset from start of MD3 object where Frame objects start. The Frame objects are written sequentially, that is, when you read one Frame object, you do not need to seek() for the next object.
+	sint32 ofs_tags;       // Relative offset from start of MD3 where Tag objects start. Similarly written sequentially.
+	sint32 ofs_surfaces;   // Relative offset from start of MD3 where Surface objects start. Again, written sequentially.
+	sint32 ofs_eof;        // Relative offset from start of MD3 to the end of the MD3 object. Note there is no offset for Skin objects.
+};
+
+struct md3_frame_t
+{
+	md3_vec3_t min_bounds;
+	md3_vec3_t max_bounds;
+	md3_vec3_t local_origin;
+	float      radius;
+	uint8      name[16];
+};
+
+struct md3_tag_t
+{
+	uint8      name[64];
+	md3_vec3_t origin;
+	md3_vec3_t axis[3];
+};
+
+struct md3_surface_header_t
+{
+	char   ident[4]; // IDP3
+	uint8  name[64]; // path name
+	sint32 flags;
+	sint32 num_frames;
+	sint32 num_shaders;
+	sint32 num_verts;
+	sint32 num_triangles;
+	sint32 ofs_triangles;
+	sint32 ofs_shaders;
+	sint32 ofs_st;
+	sint32 ofs_xyznormal;
+	sint32 ofs_end;
+};
+
+struct md3_shader_t
+{
+	uint8  name[64]; 
+	sint32 shader_index;
+};
+
+struct md3_triangle_t
+{
+	sint32 indexes[3];
+};
+
+struct md3_texcoord_t
+{
+	float  st[2];
+};
+
+struct md3_vertex_t
+{
+	sint16 x;
+	sint16 y;
+	sint16 z;
+	uint16 normal;
+};
+
+struct md3_surface_t
+{
+	md3_surface_header_t header;
+	md3_shader_t*        shaders;
+	md3_triangle_t*      triangles;
+	md3_texcoord_t*      st;
+	md3_vertex_t*        vertices;
+};
+
+struct md3_t
+{
+	md3_header_t   header;
+	md3_frame_t*   frames;
+	md3_tag_t*     tags;
+	md3_surface_t* surfaces;
+	// extra information (not in md3 file)
+	sint32         vertices_per_frame;
+};
+
+static bool check_md3_magic( char* magic )
+{
+	return magic[0] == 'I' && magic[1] == 'D' && magic[2] == 'P' && magic[3] == '3';
+}
+
+static void free_md3_surface( md3_surface_t * surface )
+{
+	delete[] surface->shaders;
+	delete[] surface->triangles;
+	delete[] surface->st;
+	delete[] surface->vertices;
+}
+
+static void free_md3( md3_t * md3 )
+{
+	sint32 count = md3->header.num_surfaces;
+	for ( sint32 i = 0; i < count; ++i )
+	{
+		free_md3_surface( &md3->surfaces[i] );
+	}
+	delete[] md3->frames;
+	delete[] md3->tags;
+	delete[] md3->surfaces;
+}
+
+static bool read_surface( md3_surface_t * surface, nv::stream& source )
+{
+	sint32 pos = static_cast< sint32 >( source.tell() );
+	source.read( &surface->header, sizeof(md3_surface_header_t), 1 );
+
+	if ( !check_md3_magic( surface->header.ident ) )          return false;
+	if ( surface->header.num_frames    >  MD3_MAX_FRAMES )    return false;
+	if ( surface->header.num_shaders   >  MD3_MAX_SHADERS )   return false;
+	if ( surface->header.num_verts     >  MD3_MAX_VERTS )     return false;
+	if ( surface->header.num_triangles >  MD3_MAX_TRIANGLES ) return false;
+
+	surface->shaders   = new md3_shader_t  [ surface->header.num_shaders ];
+	surface->vertices  = new md3_vertex_t  [ surface->header.num_verts * surface->header.num_frames ];
+	surface->st        = new md3_texcoord_t[ surface->header.num_verts ];
+	surface->triangles = new md3_triangle_t[ surface->header.num_triangles ];
+
+	source.seek( pos + surface->header.ofs_shaders, origin::SET );
+	source.read( surface->shaders, sizeof( md3_shader_t ), static_cast<nv::size_t>( surface->header.num_shaders ) );
+
+	source.seek( pos + surface->header.ofs_triangles, origin::SET );
+	source.read( surface->triangles, sizeof( md3_triangle_t ), static_cast<nv::size_t>( surface->header.num_triangles ) );
+
+	source.seek( pos + surface->header.ofs_st, origin::SET );
+	source.read( surface->st, sizeof( md3_texcoord_t ), static_cast<nv::size_t>( surface->header.num_verts ) );
+
+	source.seek( pos + surface->header.ofs_xyznormal, origin::SET );
+	source.read( surface->vertices, sizeof( md3_vertex_t ), static_cast<nv::size_t>( surface->header.num_verts * surface->header.num_frames ) );
+
+	if ( source.tell() != static_cast<nv::size_t>( pos + surface->header.ofs_end ) ) return false;
+
+	return true;
+}
+
+static bool read_md3( md3_t * md3, nv::stream& source )
+{
+	md3->frames   = nullptr;
+	md3->tags     = nullptr;
+	md3->surfaces = nullptr;
+
+	source.read( &md3->header, sizeof(md3_header_t), 1 );
+
+	if ( !check_md3_magic( md3->header.ident ) )        return false;
+	if ( md3->header.num_frames   >  MD3_MAX_FRAMES )   return false;
+	if ( md3->header.num_tags     >  MD3_MAX_TAGS )     return false;
+	if ( md3->header.num_surfaces >  MD3_MAX_SURFACES ) 
+	{
+		// to always have a safe free
+		md3->header.num_surfaces = 0;
+		return false;
+	}
+
+	md3->frames   = new md3_frame_t  [ md3->header.num_frames ];
+	md3->tags     = new md3_tag_t    [ md3->header.num_tags * md3->header.num_frames ];
+	md3->surfaces = new md3_surface_t[ md3->header.num_surfaces ];
+	nv::raw_zero_n( md3->surfaces, static_cast< nv::size_t >( md3->header.num_surfaces ) );
+
+	source.seek( md3->header.ofs_frames, origin::SET );
+	source.read( md3->frames, sizeof( md3_frame_t ), static_cast<nv::size_t>( md3->header.num_frames ) );
+
+	if ( md3->header.num_tags > 0 )
+	{
+		source.seek( md3->header.ofs_tags, origin::SET );
+		source.read( md3->tags, sizeof( md3_tag_t ), static_cast<nv::size_t>( md3->header.num_tags * md3->header.num_frames ) );
+	}
+
+	source.seek( md3->header.ofs_surfaces, origin::SET );
+	md3->vertices_per_frame = 0;
+
+	for ( sint32 i = 0; i < md3->header.num_surfaces; ++i )
+	{
+		if ( !read_surface( md3->surfaces + i, source ) ) return false;
+		if ( md3->header.num_frames != md3->surfaces[i].header.num_frames ) return false;
+		md3->vertices_per_frame += md3->surfaces[i].header.num_verts;
+	}
+	return true;
+}
+
+static inline vec3 md3_vec3( const md3_vec3_t& v )
+{
+//	return vec3( v.xyz[0], v.xyz[1], v.xyz[2] );
+	return vec3( v.xyz[0], v.xyz[2], v.xyz[1] );
+}
+
+static inline vec2 md3_texcoord( const md3_texcoord_t& v )
+{
+	return vec2( v.st[0], v.st[1] );
+}
+
+static vec3 s_normal_cache[256*256];
+static bool s_normal_ready = false;
+
+md3_loader::md3_loader( string_table* strings, bool merge_all )
+	: mesh_loader( strings ), m_merge_all( merge_all ), m_md3( nullptr )
+{
+	if ( !s_normal_ready )
+	{
+		float pi      = math::pi<float>();
+		float convert = (2 * pi) / 255.0f;
+		int n = 0;
+		for ( int lat = 0; lat < 256; ++lat )
+		{
+			float flat    = lat * convert;
+			float sin_lat = nv::sin( flat );
+			float cos_lat = nv::cos( flat );
+			for ( int lng = 0; lng < 256; ++lng, ++n )
+			{
+				float flng    = lng * convert;
+				float sin_lng = nv::sin( flng );
+				float cos_lng = nv::cos( flng );
+				s_normal_cache[n].x = cos_lat * sin_lng;
+//				s_normal_cache[n].y = sin_lat * sin_lng;
+//				s_normal_cache[n].z = cos_lng;
+				s_normal_cache[n].z = sin_lat * sin_lng;
+				s_normal_cache[n].y = cos_lng;
+			}
+		}
+
+		s_normal_ready = true;
+	}
+}
+
+
+nv::md3_loader::~md3_loader()
+{
+	md3_t* md3 = reinterpret_cast< md3_t* >( m_md3 );
+	if ( md3 != nullptr )
+	{
+		free_md3( md3 );
+		delete md3;
+	}
+}
+
+bool nv::md3_loader::load( stream& source )
+{
+	md3_t* md3 = new md3_t;
+	m_md3 = md3;
+	if ( !read_md3( md3, source ) )
+	{
+		return false;
+	}
+	return true;
+}
+
+void nv::md3_loader::load_tags( raw_data_channel* channel, const string_view& tag )
+{
+	md3_t* md3 = reinterpret_cast< md3_t* >( m_md3 );
+	data_channel_access< md3_key > access( channel );
+	// TODO: is this brain damaged in efficiency (loop nest order) or what?
+	for ( sint32 f = 0; f < md3->header.num_frames; ++f )
+	{
+		for ( sint32 i = 0; i < md3->header.num_tags; ++i )
+		{
+			const md3_tag_t& rtag = md3->tags[i + md3->header.num_tags * f];
+			string_view rname( reinterpret_cast< const char* >(rtag.name) );
+			if (rname == tag)
+			{
+				vec3 axisx  ( md3_vec3( rtag.axis[0] ) );
+				vec3 axisz  ( md3_vec3( rtag.axis[1] ) );
+				vec3 axisy  ( md3_vec3( rtag.axis[2] ) );
+				vec3 origin ( md3_vec3( rtag.origin )  );
+				access.data()[f].tform = transform( origin, quat( mat3( axisx, axisy, axisz ) ) );
+			}
+		}
+
+	}
+}
+
+struct vtx_md3_pn
+{
+	nv::vec3 position;
+	nv::vec3 normal;
+};
+
+struct vtx_md3_t
+{
+	nv::vec2 texcoord;
+};
+
+data_channel_set* nv::md3_loader::release_mesh_data( nv::size_t index )
+{
+	data_channel_set* data = data_channel_set_creator::create_set(3);
+	release_mesh_frame( data, -1, static_cast< sint32 >( index ) );
+	return data;
+}
+
+void nv::md3_loader::release_mesh_frame( data_channel_set* data, sint32 frame, sint32 surface )
+{
+	md3_t* md3 = reinterpret_cast< md3_t* >( m_md3 );
+	sint32 num_surfaces  = md3->header.num_surfaces;
+	sint32 num_verts     = 0;
+	sint32 current_frame = ( frame == -1 ? 0 : frame );
+	sint32 frame_count   = ( frame == -1 ? md3->header.num_frames : 1 );
+	sint32 current_surf  = ( surface == -1 ? 0 : surface );
+	sint32 surf_count    = ( surface == -1 ? md3->header.num_surfaces : 1 );
+	sint32 index_count   = 0;
+
+	if ( surface >= 0 )
+	{
+		index_count = md3->surfaces[surface].header.num_triangles * 3;
+		num_verts   = md3->surfaces[surface].header.num_verts;
+	}
+	else
+		for ( sint32 i = 0; i < num_surfaces; ++i )
+		{
+			index_count += md3->surfaces[i].header.num_triangles * 3;
+			num_verts   += md3->surfaces[i].header.num_verts;
+		}
+
+	data_channel_set_creator maccess( data );
+	maccess.set_name( make_name( reinterpret_cast<char*>( md3->header.name ) ) );
+
+
+	vtx_md3_pn* vtx_pn = maccess.add_channel< vtx_md3_pn >( static_cast< uint32 >( num_verts * frame_count ) ).data();
+	vtx_md3_t*  vtx_t  = maccess.add_channel< vtx_md3_t >( static_cast< uint32 >( num_verts ) ).data();
+	uint16*     icp    = reinterpret_cast< uint16* >( maccess.add_channel< index_u16 >( static_cast< uint32 >( index_count ) ).data() );
+
+	uint32 index  = 0;
+	uint32 iindex = 0;
+	sint32 index_base = 0;
+
+	while ( surf_count > 0 )
+	{
+		const md3_surface_t& sface  = md3->surfaces[ current_surf ];
+		const uint32         vcount = static_cast< uint32 >( sface.header.num_verts );
+		const uint32         tcount = static_cast< uint32 >( sface.header.num_triangles );
+
+		for (uint32 j = 0; j < vcount; ++j )
+		{
+			vtx_t[index++].texcoord = md3_texcoord( sface.st[j] );
+		}
+
+		for (size_t j = 0; j < tcount; ++j )
+		{
+			const md3_triangle_t& t = sface.triangles[j];
+			icp[iindex++] = static_cast< uint16 >( index_base + t.indexes[0] );
+			icp[iindex++] = static_cast< uint16 >( index_base + t.indexes[1] );
+			icp[iindex++] = static_cast< uint16 >( index_base + t.indexes[2] );
+		}
+		index_base += sface.header.num_verts;
+		++current_surf;
+		--surf_count;
+	}
+
+	index = 0;
+	while ( frame_count > 0 )
+	{
+		current_surf  = ( surface == -1 ? 0 : surface );
+		surf_count    = ( surface == -1 ? md3->header.num_surfaces : 1 );
+
+		while ( surf_count > 0 )
+		{
+			md3_surface_t& sface  = md3->surfaces[current_surf];
+			sint32 vcount = sface.header.num_verts;
+			sint32 offset = vcount * current_frame;
+			sint32 limit  = vcount + offset;
+			for ( sint32 j = offset; j < limit; ++j )
+			{
+				md3_vertex_t& v = sface.vertices[j];
+				vtx_pn[index].position = vec3( v.x * MD3_XYZ_SCALE, v.z * MD3_XYZ_SCALE, v.y * MD3_XYZ_SCALE );
+				vtx_pn[index].normal   = s_normal_cache[ v.normal ];
+				index++;
+			}
+			++current_surf;
+			--surf_count;
+		}
+		++current_frame;
+		--frame_count;
+	}
+
+}
+
+mesh_nodes_data* nv::md3_loader::release_mesh_nodes_data( nv::size_t )
+{
+	md3_t* md3 = reinterpret_cast< md3_t* >( m_md3 );
+	uint32 node_count = uint32( md3->header.num_tags );
+	if ( node_count == 0 ) return nullptr;
+	mesh_nodes_data* result = new mesh_nodes_data( m_strings ? m_strings->insert( "tags" ) : shash64() );
+	for ( uint32 i = 0; i < node_count; ++i )
+	{
+		const md3_tag_t& rtag = md3->tags[i];
+		string_view name( reinterpret_cast< const char* >(rtag.name) );
+		data_channel_set* set = data_channel_set_creator::create_set( 1 );
+		data_channel_set_creator access( set );
+		access.set_name( make_name( name ) );
+		load_tags( access.add_channel<md3_key>( uint32( md3->header.num_frames ) ).channel(), name );
+		result->append( set );
+	}
+	result->initialize();
+	return result;
+}
+
+nv::size_t md3_loader::get_max_frames() const
+{
+	return static_cast<size_t>( reinterpret_cast<md3_t*>( m_md3 )->header.num_frames );
+}
Index: /trunk/legacy/md3_loader.hh
===================================================================
--- /trunk/legacy/md3_loader.hh	(revision 480)
+++ /trunk/legacy/md3_loader.hh	(revision 480)
@@ -0,0 +1,50 @@
+// Copyright (C) 2012-2015 ChaosForge Ltd
+// http://chaosforge.org/
+//
+// This file is part of Nova libraries. 
+// For conditions of distribution and use, see copying.txt file in root folder.
+
+/**
+ * @file md3_loader.hh
+ * @author Kornel Kisielewicz
+ * @brief md3 loader
+ */
+
+#ifndef NV_FORMATS_MD3_LOADER_HH
+#define NV_FORMATS_MD3_LOADER_HH
+
+#include <nv/common.hh>
+#include <nv/core/transform.hh>
+#include <nv/interface/mesh_data.hh>
+#include <nv/interface/mesh_loader.hh>
+
+namespace nv 
+{
+	struct md3_key
+	{
+		transform tform;
+	};
+
+
+	class md3_loader : public mesh_loader
+	{
+	public:
+		explicit md3_loader( string_table* strings, bool merge_all = true );
+		virtual ~md3_loader();
+		virtual bool load( stream& source );
+		virtual data_channel_set* release_mesh_data( size_t index = 0 );
+		virtual size_t get_mesh_count() const { return 1; }
+		size_t get_max_frames() const;
+		virtual size_t get_nodes_data_count() const { return 1; }
+		virtual mesh_nodes_data* release_mesh_nodes_data( size_t = 0 );
+	private:
+		void release_mesh_frame( data_channel_set* data, sint32 frame, sint32 surface );
+		void load_tags( raw_data_channel* channel, const string_view& tag );
+
+		bool m_merge_all;
+		void* m_md3;
+	};
+
+}
+
+#endif // NV_FORMATS_MD3_LOADER_HH
Index: /trunk/legacy/skeletal_mesh.cc
===================================================================
--- /trunk/legacy/skeletal_mesh.cc	(revision 480)
+++ /trunk/legacy/skeletal_mesh.cc	(revision 480)
@@ -0,0 +1,41 @@
+// Copyright (C) 2011-2015 ChaosForge Ltd
+// http://chaosforge.org/
+//
+// This file is part of Nova libraries. 
+// For conditions of distribution and use, see copying.txt file in root folder.
+
+#include "nv/gfx/skeletal_mesh.hh"
+
+#include "nv/interface/context.hh"
+#include "nv/interface/device.hh"
+#include "nv/stl/unordered_map.hh"
+
+#include "nv/core/logging.hh"
+#if 0
+void nv::skeletal_animation_entry::update_skeleton( skeleton_instance& data, uint32 a_ms_time ) const
+{
+	float  fframe = ( a_ms_time * 0.001f ) * m_fps;
+	float  nframe = nv::floor( fframe );
+	uint32 duration = get_frame_count();
+	if ( duration == 0 )
+	{
+		fframe = static_cast<float>( get_start_frame() );
+	}
+	else if ( nframe >= duration )
+	{
+		if ( is_looping() )
+			fframe = nv::fmodf( fframe, nv::f32( duration ) );
+		else
+			fframe = static_cast<float>( get_end_frame() );
+	}
+
+	if ( data.size() == 0 )
+		data.initialize( m_temp_anim->size() );
+	data.animate( m_temp_anim, m_data, fframe );
+}
+
+void nv::skeletal_animation_entry::prepare( const mesh_nodes_data* bones )
+{
+	m_data.prepare( m_temp_anim, bones ? bones : m_temp_anim );
+}
+#endif
Index: /trunk/legacy/skeletal_mesh.hh
===================================================================
--- /trunk/legacy/skeletal_mesh.hh	(revision 480)
+++ /trunk/legacy/skeletal_mesh.hh	(revision 480)
@@ -0,0 +1,39 @@
+// Copyright (C) 2012-2015 ChaosForge Ltd
+// http://chaosforge.org/
+//
+// This file is part of Nova libraries. 
+// For conditions of distribution and use, see copying.txt file in root folder.
+
+#ifndef NV_GFX_SKELETAL_MESH_HH
+#define NV_GFX_SKELETAL_MESH_HH
+
+#include <nv/common.hh>
+#include <nv/interface/context.hh>
+#include <nv/interface/animated_mesh.hh>
+#include <nv/stl/array.hh>
+#include <nv/gfx/skeleton_instance.hh>
+
+namespace nv
+{
+#if 0	
+ 	class skeletal_animation_entry : public animation_entry
+ 	{
+	public:
+		skeletal_animation_entry( shash64 name, const mesh_nodes_data* anim, uint32 fps, uint32 time_start, uint32 time_end, bool a_looping )
+			: animation_entry( name, a_looping, fps, time_start, time_end )
+			, m_temp_anim( anim )
+		{
+		}
+		const skeleton_binding& get_binding() const { return m_data; }
+		void prepare( const mesh_nodes_data* bones );
+		void update_skeleton( skeleton_instance& tr, uint32 a_ms_time ) const;
+		
+	protected:
+		const mesh_nodes_data* m_temp_anim;
+		skeleton_binding m_data;
+ 	};
+#endif
+
+} // namespace nv
+
+#endif // NV_GFX_SKELETAL_MESH_HH
Index: /trunk/nv/core/resource.hh
===================================================================
--- /trunk/nv/core/resource.hh	(revision 479)
+++ /trunk/nv/core/resource.hh	(revision 480)
@@ -22,4 +22,5 @@
 {
 
+	struct resource_unchecked {};
 	template < typename T > class resource;
 	template < typename T > class resource_handle;
@@ -32,5 +33,5 @@
 
 	protected:
-		virtual const void* lock( resource_id id, resource_type_id type_hash ) = 0;
+		virtual const void* lock( resource_id id, resource_type_id type_hash, bool checked = true ) = 0;
 //		virtual const void* create( resource_id id, resource_type_id type_hash ) = 0;
 		virtual void unlock( resource_id id, resource_type_id type_hash ) = 0;
@@ -38,7 +39,7 @@
 
 		template< typename T >
-		const T* lock( resource_id id )
-		{
-			return reinterpret_cast< const T* >( lock( id, shash64( rtti_type_hash<T>::hash() ) ) );
+		const T* lock( resource_id id, bool checked = true )
+		{
+			return reinterpret_cast< const T* >( lock( id, shash64( rtti_type_hash<T>::hash() ), checked ) );
 		}
 		template< typename T >
@@ -75,4 +76,5 @@
 		constexpr bool is_valid() const { return m_id && m_handler; }
 		constexpr explicit operator bool() const { return is_valid(); }
+		resource_lock< T > lock() const { return resource_lock< T >( *this, resource_unchecked() ); }
 		~resource()
 		{
@@ -146,11 +148,42 @@
 		static const uint64 hash_value = rtti_type_hash<T>::value;
 	public:
-		explicit resource_lock( const resource< T >& r ) : m_id( r.m_id ), m_handler( r.m_handler ), m_resource( r.m_handler->lock<T>( r.m_id ) ) {}
+		resource_lock() = delete;
+		explicit resource_lock( const resource< T >& r, resource_unchecked )
+			: m_id( r.m_id )
+			, m_handler( r.m_handler )
+			, m_resource( r.m_handler ? r.m_handler->lock<T>( r.m_id, false ) : nullptr ) {}
+		explicit resource_lock( const resource< T >& r ) 
+			: m_id( r.m_id )
+			, m_handler( r.m_handler )
+			, m_resource( r.m_handler->lock<T>( r.m_id ) ) {}
 		explicit resource_lock( const resource_handle< T >& r, resource_handler* handler ) : m_id( r.m_id ), m_handler( handler ), m_resource( handler->lock( r.m_id, shash64( hash_value ) ) ) {}
+		resource_lock( const resource_lock& ) = delete;
+		resource_lock& operator=( const resource_lock& other ) = delete;
+		resource_lock( resource_lock&& other )
+		{
+			m_id       = other.m_id;
+			m_handler  = other.m_handler;
+			m_resource = other.m_resource;
+			other.m_handler = nullptr;
+		}
+		resource_lock& operator=( resource_lock&& other )
+		{
+			if ( this != &other )
+			{
+				if ( m_handler ) m_handler->unlock( m_id, shash64( hash_value ) );
+				m_id       = other.m_id;
+				m_handler  = other.m_handler;
+				m_resource = other.m_resource;
+				other.m_handler = nullptr;
+			}
+			return *this;
+		}
 		const T& operator*() const { return *m_resource; }
 		const T* operator->() const { return m_resource; }
+		constexpr bool is_valid() const { return m_resource != nullptr; }
+		constexpr explicit operator bool() const { return is_valid(); }
 		~resource_lock()
 		{
-			m_handler->unlock( m_id, shash64( hash_value ) );
+			if ( m_handler ) m_handler->unlock( m_id, shash64( hash_value ) );
 		}
 	private:
Index: /trunk/nv/engine/resource_system.hh
===================================================================
--- /trunk/nv/engine/resource_system.hh	(revision 479)
+++ /trunk/nv/engine/resource_system.hh	(revision 480)
@@ -125,10 +125,4 @@
 		}
 
-		resource_type add( shash64 id, stored_type resource )
-		{
-			m_store[id] = resource;
-			return create< T >( id );
-		}
-
 		virtual ~custom_resource_manager()
 		{
@@ -136,5 +130,16 @@
 		}
 	protected:
-		virtual const void* lock( resource_id id, resource_type_id )
+		resource_type add( shash64 id, stored_type resource )
+		{
+			auto m = m_store.find( shash64( id ) );
+			if ( m != m_store.end() )
+			{
+				release( m->second );
+			}
+			m_store[id] = resource;
+			return create< T >( id );
+		}
+
+		virtual const void* lock( resource_id id, resource_type_id, bool )
 		{
 			auto m = m_store.find( id );
@@ -150,4 +155,15 @@
 	using lua_resource_manager = custom_resource_manager< T, Heap, lua_resource_manager_base >;
 
+
+	template < typename T, bool Heap = true >
+	class manual_resource_manager : public custom_resource_manager< T, Heap >
+	{
+	public:
+		manual_resource_manager() {}
+		using custom_resource_manager< T, Heap >::add;
+	protected:
+		virtual bool load_resource( const nv::string_view& ) { return false; }
+
+	};
 }
 
Index: /trunk/nv/formats/assimp_loader.hh
===================================================================
--- /trunk/nv/formats/assimp_loader.hh	(revision 479)
+++ /trunk/nv/formats/assimp_loader.hh	(revision 480)
@@ -25,5 +25,4 @@
 		virtual size_t get_mesh_count() const { return m_mesh_count; }
 		virtual ~assimp_loader();
-		virtual mesh_data_pack* release_mesh_data_pack();
 		virtual size_t get_nodes_data_count() const;
 		virtual mesh_nodes_data* release_mesh_nodes_data( size_t index = 0 );
Index: unk/nv/formats/md2_loader.hh
===================================================================
--- /trunk/nv/formats/md2_loader.hh	(revision 479)
+++ 	(revision )
@@ -1,46 +1,0 @@
-// Copyright (C) 2012-2015 ChaosForge Ltd
-// http://chaosforge.org/
-//
-// This file is part of Nova libraries. 
-// For conditions of distribution and use, see copying.txt file in root folder.
-
-/**
- * @file md2_loader.hh
- * @author Kornel Kisielewicz
- * @brief md2 loader
- */
-
-#ifndef NV_FORMATS_MD2_LOADER_HH
-#define NV_FORMATS_MD2_LOADER_HH
-
-#include <nv/common.hh>
-#include <nv/stl/vector.hh>
-#include <nv/interface/mesh_loader.hh>
-
-namespace nv 
-{
-
-	class md2_loader : public mesh_loader
-	{
-	public:
-		explicit md2_loader( string_table* strings );
-		virtual ~md2_loader();
-		virtual bool load( stream& source );
-		virtual data_channel_set* release_mesh_data( size_t index = 0 );
-		virtual mesh_data_pack* release_mesh_data_pack();
-		virtual size_t get_mesh_count() const { return 1; }
-	private:
-		void release_mesh_frame( data_channel_set* data, sint32 frame );
-		size_t get_max_frames() const;
-		void reindex();
-	private:
-		void* m_md2;
-		string_table* m_strings;
-		vector< uint16 > m_new_indexes;
-		vector< uint16 > m_new_vindexes;
-		vector< uint16 > m_new_tindexes;
-	};
-
-}
-
-#endif // NV_FORMATS_MD2_LOADER_HH
Index: unk/nv/formats/md3_loader.hh
===================================================================
--- /trunk/nv/formats/md3_loader.hh	(revision 479)
+++ 	(revision )
@@ -1,51 +1,0 @@
-// Copyright (C) 2012-2015 ChaosForge Ltd
-// http://chaosforge.org/
-//
-// This file is part of Nova libraries. 
-// For conditions of distribution and use, see copying.txt file in root folder.
-
-/**
- * @file md3_loader.hh
- * @author Kornel Kisielewicz
- * @brief md3 loader
- */
-
-#ifndef NV_FORMATS_MD3_LOADER_HH
-#define NV_FORMATS_MD3_LOADER_HH
-
-#include <nv/common.hh>
-#include <nv/core/transform.hh>
-#include <nv/interface/mesh_data.hh>
-#include <nv/interface/mesh_loader.hh>
-
-namespace nv 
-{
-	struct md3_key
-	{
-		transform tform;
-	};
-
-
-	class md3_loader : public mesh_loader
-	{
-	public:
-		explicit md3_loader( string_table* strings, bool merge_all = true );
-		virtual ~md3_loader();
-		virtual bool load( stream& source );
-		virtual data_channel_set* release_mesh_data( size_t index = 0 );
-		virtual mesh_data_pack* release_mesh_data_pack();
-		virtual size_t get_mesh_count() const { return 1; }
-		size_t get_max_frames() const;
-		virtual size_t get_nodes_data_count() const { return 1; }
-		virtual mesh_nodes_data* release_mesh_nodes_data( size_t = 0 );
-	private:
-		void release_mesh_frame( data_channel_set* data, sint32 frame, sint32 surface );
-		void load_tags( raw_data_channel* channel, const string_view& tag );
-
-		bool m_merge_all;
-		void* m_md3;
-	};
-
-}
-
-#endif // NV_FORMATS_MD3_LOADER_HH
Index: /trunk/nv/formats/nmd_loader.hh
===================================================================
--- /trunk/nv/formats/nmd_loader.hh	(revision 479)
+++ /trunk/nv/formats/nmd_loader.hh	(revision 480)
@@ -83,5 +83,4 @@
 		virtual size_t get_nodes_data_count() const { return 1; }
 		virtual mesh_nodes_data* release_mesh_nodes_data( size_t = 0 );
-		virtual mesh_data_pack* release_mesh_data_pack();
 		virtual size_t get_mesh_count() const { return m_meshes.size(); }
 		virtual ~nmd_loader();
@@ -104,5 +103,7 @@
 	void nmd_dump_element( stream& stream_out, const data_channel_set& data, nmd_type type );
 	void nmd_dump_nodes( stream& stream_out, const mesh_nodes_data& nodes );
-	void nmd_dump( stream& stream_out, const mesh_data_pack* model, const string_table* strings = nullptr, uint64 name = 0 );
+	void nmd_dump( stream& stream_out, array_view< data_channel_set* > meshes, const mesh_nodes_data* nodes, const string_table* strings = nullptr, uint64 name = 0 );
+	void nmd_dump( stream& stream_out, const mesh_nodes_data& animation, const string_table* strings = nullptr, uint64 name = 0 );
+
 
 }
Index: /trunk/nv/formats/obj_loader.hh
===================================================================
--- /trunk/nv/formats/obj_loader.hh	(revision 479)
+++ /trunk/nv/formats/obj_loader.hh	(revision 480)
@@ -28,5 +28,4 @@
 		virtual bool load( stream& source );
 		virtual data_channel_set* release_mesh_data( size_t index = 0 );
-		virtual mesh_data_pack* release_mesh_data_pack();
 		virtual size_t get_mesh_count() const { return m_meshes.size(); }
 		~obj_loader();
Index: unk/nv/gfx/keyframed_mesh.hh
===================================================================
--- /trunk/nv/gfx/keyframed_mesh.hh	(revision 479)
+++ 	(revision )
@@ -1,99 +1,0 @@
-// Copyright (C) 2012-2015 ChaosForge Ltd
-// http://chaosforge.org/
-//
-// This file is part of Nova libraries. 
-// For conditions of distribution and use, see copying.txt file in root folder.
-
-#ifndef NV_GFX_KEYFRAMED_MESH_HH
-#define NV_GFX_KEYFRAMED_MESH_HH
-
-#include <nv/common.hh>
-#include <nv/interface/context.hh>
-#include <nv/interface/animated_mesh.hh>
-#include <nv/formats/md3_loader.hh>
-
-namespace nv
-{
-
-	class keyframed_mesh : public animated_mesh
-	{
-	public:
-		keyframed_mesh( context* a_context, const data_channel_set* a_data, const mesh_nodes_data* a_tag_map );
-		virtual vertex_array get_vertex_array() const { return m_va; }
-		virtual size_t get_index_count() const { return m_index_count; }
-		virtual void run_animation( animation_entry* a_anim );
-		size_t get_max_frames() const;
-		virtual transform get_node_transform( uint32 node_id ) const;
-		virtual mat4 get_node_matrix( uint32 node_id ) const;
-		virtual void update_animation( animation_entry*, uint32 a_ms_anim_time );
-		virtual void update( program a_program );
-		virtual ~keyframed_mesh();
-	protected:
-		virtual void set_frame( uint32 frame );
-
-		struct vertex_pn
-		{
-			vec3 position;
-			vec3 normal;
-		};
-		struct vertex_pnt
-		{
-			vec3 position;
-			vec3 normal;
-			vec4 tangent;
-		};
-		struct vertex_t
-		{
-			vec2 texcoord;
-		};
-
-		context*     m_context;
-
-		const mesh_nodes_data*  m_tag_map;
-
-		data_descriptor m_interpolation_key;
-		buffer       m_pbuffer;
-		vertex_array m_va;
-
-		uint32 m_last_frame;
-		uint32 m_next_frame;
-		uint32 m_vsize;
-		f32    m_interpolation;
-		bool   m_has_tangent;
-		bool   m_active;
-
-		uint32 m_index_count;
-		uint32 m_frame_count;
-		uint32 m_vertex_count;
-	};
-
-
-	class keyframed_mesh_gpu : public keyframed_mesh
-	{
-	public:
-		keyframed_mesh_gpu( context* a_context, const data_channel_set* a_data, const mesh_nodes_data* a_tag_map );
-		void update_animation( animation_entry* anim, uint32 a_anim_time );
-		virtual void update( program a_program );
-	private:
-		int m_loc_next_position;
-		int m_loc_next_normal;
-		int m_loc_next_tangent;
-
-		uint32 m_gpu_last_frame;
-		uint32 m_gpu_next_frame;
-	};
-
-	class keyframed_mesh_cpu : public keyframed_mesh
-	{
-	public:
-		keyframed_mesh_cpu( context* a_context, const data_channel_set* a_data, const mesh_nodes_data* a_tag_map );
-		void update_animation( animation_entry* anim, uint32 a_anim_time );
-		~keyframed_mesh_cpu();
-	private:
-		const uint8* m_model_data;
-		uint8*  m_data;
-	};
-
-} // namespace nv
-
-#endif // NV_GFX_KEYFRAMED_MESH_HH
Index: /trunk/nv/gfx/mesh_creator.hh
===================================================================
--- /trunk/nv/gfx/mesh_creator.hh	(revision 479)
+++ /trunk/nv/gfx/mesh_creator.hh	(revision 480)
@@ -18,5 +18,9 @@
 	{
 	public:
-		mesh_data_creator( data_channel_set* data );
+		mesh_data_creator( data_channel_set* data ) : m_data( data )
+		{
+			initialize();
+		}
+
 		// assumes that position and normal is vec3, tangent is vec4
 		void transform( float scale, const mat3& r33 );
@@ -64,5 +68,6 @@
 	{
 	public:
-		mesh_nodes_creator( mesh_nodes_data* data ) : m_data( data ) {}
+		mesh_nodes_creator( mesh_nodes_data* data ) : m_data( data )
+		{}
 		// assumes that keys are equally spaced
 		void pre_transform_keys();
@@ -74,54 +79,4 @@
 	};
 
-	class mesh_creator
-	{
-	public:
-		mesh_creator( mesh_data_pack* pack ) : m_pack( pack ) {}
-		void delete_mesh( uint32 index );
-		// assumes that keys are equally spaced
-		void pre_transform_keys() 
-		{
-			if ( m_pack->m_nodes )
-				mesh_nodes_creator( m_pack->m_nodes ).pre_transform_keys();
-		}
-		void generate_tangents()
-		{
-			for ( size_t m = 0; m < m_pack->m_count; ++m )
-				mesh_data_creator( &(m_pack->m_meshes[m]) ).generate_tangents();
-		}
-
-		// assumes that keys are equally spaced
-		void merge_keys() { mesh_nodes_creator( m_pack->m_nodes ).merge_keys(); }
-		// assumes that position and normal is vec3, tangent is vec4
-		void flip_normals() 
-		{
-			for ( size_t m = 0; m < m_pack->m_count; ++m )
-				mesh_data_creator( &(m_pack->m_meshes[m]) ).flip_normals();
-		}
-		void transform( float scale, const mat3& r33 ) 
-		{
-			for ( size_t m = 0; m < m_pack->m_count; ++m )
-				mesh_data_creator( &(m_pack->m_meshes[m]) ).transform( scale, r33 );
-			if ( m_pack->m_nodes )
-				mesh_nodes_creator( m_pack->m_nodes ).transform( scale, r33 );
-		}
-		// assumes models have same format
-		// currently only data merge
-		bool merge( mesh_data_pack* other )
-		{
-			for ( size_t m = 0; m < m_pack->m_count; ++m )
-			{
-				if (!mesh_data_creator( &(m_pack->m_meshes[m]) ).is_same_format( &(other->m_meshes[m]) ) ) 
-					return false;
-			}
-			for ( size_t m = 0; m < m_pack->m_count; ++m )
-			{
-				mesh_data_creator(&(m_pack->m_meshes[m]) ).merge( &(other->m_meshes[m]) );
-			}
-			return true;
-		}
-	private:
-		mesh_data_pack* m_pack;
-	};
 }
 
Index: unk/nv/gfx/skeletal_mesh.hh
===================================================================
--- /trunk/nv/gfx/skeletal_mesh.hh	(revision 479)
+++ 	(revision )
@@ -1,70 +1,0 @@
-// Copyright (C) 2012-2015 ChaosForge Ltd
-// http://chaosforge.org/
-//
-// This file is part of Nova libraries. 
-// For conditions of distribution and use, see copying.txt file in root folder.
-
-#ifndef NV_GFX_SKELETAL_MESH_HH
-#define NV_GFX_SKELETAL_MESH_HH
-
-#include <nv/common.hh>
-#include <nv/interface/context.hh>
-#include <nv/interface/animated_mesh.hh>
-#include <nv/stl/array.hh>
-#include <nv/gfx/skeleton_instance.hh>
-
-namespace nv
-{
-	
- 	class skeletal_animation_entry : public animation_entry
- 	{
-	public:
-		skeletal_animation_entry( shash64 name, const mesh_nodes_data* anim, uint32 fps, uint32 time_start, uint32 time_end, bool a_looping )
-			: animation_entry( name, a_looping, fps, time_start, time_end )
-			, m_temp_anim( anim )
-		{
-		}
-		const skeleton_binding& get_binding() const { return m_data; }
-		void prepare( const mesh_nodes_data* bones );
-		void update_skeleton( skeleton_instance& tr, uint32 a_ms_time ) const;
-		
-	protected:
-		const mesh_nodes_data* m_temp_anim;
-		skeleton_binding m_data;
- 	};
-
-	class skeletal_mesh : public animated_mesh
-	{
-	public:
-		skeletal_mesh( context* a_context, const data_channel_set* a_mesh, const mesh_nodes_data* a_bone_data );
-		virtual vertex_array get_vertex_array() const { return m_va; }
-		virtual size_t get_index_count() const { return m_index_count; }
-		virtual void run_animation( animation_entry& a_anim )
-		{
-			update_animation( a_anim, 0 );
-		}
-		virtual void update_program( program a_program );
-		virtual void update_animation( animation_entry& a_anim, uint32 a_anim_time );
-		virtual transform get_node_transform( uint32 node_id ) const;
-		virtual mat4 get_node_matrix( uint32 node_id ) const;
-		virtual sint16 get_parent_id() const { return m_parent_id; }
-		virtual uint32 get_node_count() const { return m_skeleton.size(); }
-		skeleton_instance& get_skeleton() { return m_skeleton; }
-		const mesh_nodes_data* get_bone_data() { return m_bone_data; }
-		context* get_context() { return m_context;  }
-		~skeletal_mesh()
-		{
-			m_context->release( m_va );
-		}
-	protected:
-		skeleton_instance m_skeleton;
-		vertex_array m_va;
-		uint32       m_index_count;
-		context*     m_context;
-		const mesh_nodes_data* m_bone_data;
-		sint16 m_parent_id;
-	};
-
-} // namespace nv
-
-#endif // NV_GFX_SKELETAL_MESH_HH
Index: /trunk/nv/gfx/skeleton_instance.hh
===================================================================
--- /trunk/nv/gfx/skeleton_instance.hh	(revision 479)
+++ /trunk/nv/gfx/skeleton_instance.hh	(revision 480)
@@ -55,5 +55,5 @@
 		void animate( const mesh_nodes_data* node_data, const skeleton_binding& binding, float frame );
 	protected:
-		void animate_rec( const mesh_nodes_data* node_data, const skeleton_binding& binding, float frame, uint32 id, const mat4& parent );
+		void animate_rec( const mesh_nodes_data* node_data, const skeleton_binding& binding, float frame, uint32 id, const transform& parent );
 		void animate_flat( const mesh_nodes_data* node_data, const skeleton_binding& binding, float frame );
 
Index: /trunk/nv/interface/animated_mesh.hh
===================================================================
--- /trunk/nv/interface/animated_mesh.hh	(revision 479)
+++ /trunk/nv/interface/animated_mesh.hh	(revision 480)
@@ -22,4 +22,5 @@
 {
 
+#if 0
 	class animation_entry
 	{
@@ -51,18 +52,5 @@
 		uint32  m_fend;    /// end time (in frames)
 	};
-
-	class animated_mesh : public mesh_interface
-	{
-	public:
-		animated_mesh() {}
-		virtual void update_animation( animation_entry&, uint32 ) {}
-		virtual void run_animation( animation_entry& ) {}
-		virtual transform get_node_transform( uint32 ) const { return transform(); }
-		virtual mat4 get_node_matrix( uint32 ) const { return mat4(); }
-		virtual uint32 get_node_count() const { return 0; }
-		virtual sint16 get_parent_id() const { return -1; }
-
-	};
-
+#endif
 
 }
Index: /trunk/nv/interface/context.hh
===================================================================
--- /trunk/nv/interface/context.hh	(revision 479)
+++ /trunk/nv/interface/context.hh	(revision 480)
@@ -43,15 +43,4 @@
 		DRAW_FRAMEBUFFER,
 		FRAMEBUFFER,
-	};
-
-	class mesh_interface
-	{
-	public:
-		mesh_interface() {}
-		virtual void update_program( program ) {}
-		virtual nv::vertex_array get_vertex_array() const = 0;
-		virtual size_t get_index_count() const = 0;
-		virtual nv::primitive get_primitive_type() const { return nv::TRIANGLES; }
-		virtual ~mesh_interface() {}
 	};
 
Index: /trunk/nv/interface/mesh_data.hh
===================================================================
--- /trunk/nv/interface/mesh_data.hh	(revision 479)
+++ /trunk/nv/interface/mesh_data.hh	(revision 480)
@@ -152,43 +152,4 @@
 	};
 
-	class mesh_data_pack
-	{
-		friend class mesh_creator;
-	public:
-		explicit mesh_data_pack( uint32 a_count, data_channel_set* a_meshes, mesh_nodes_data* a_nodes = nullptr )
-		{
-			m_count  = a_count;
-			m_meshes = a_meshes;
-			m_nodes  = a_nodes;
-		}
-		const data_channel_set* get_mesh( uint32 index ) const
-		{
-			if ( index >= m_count ) return nullptr;
-			return &m_meshes[ index ];
-		}
-		const data_channel_set* get_mesh_by_hash( shash64 h ) const
-		{
-			for ( uint32 i = 0; i < m_count; ++i )
-			{
-				if ( m_meshes[i].get_name() == h )
-					return &m_meshes[i];
-			}
-			return nullptr;
-		}
-		uint32 get_count() const { return m_count; }
-		data_channel_set* release_meshes() { data_channel_set* meshes = m_meshes; m_meshes = nullptr; return meshes; }
-		mesh_nodes_data* release_nodes() { mesh_nodes_data* nodes = m_nodes; m_nodes = nullptr; return nodes; }
-		const mesh_nodes_data* get_nodes() const { return m_nodes; }
-		uint32 get_node_count() const { return m_nodes ? m_nodes->size() : 0; }
-		~mesh_data_pack()
-		{
-			delete[] m_meshes;
-			delete   m_nodes;
-		}
-	private:
-		uint32            m_count;
-		data_channel_set* m_meshes;
-		mesh_nodes_data*  m_nodes;
-	};
 }
 
Index: /trunk/nv/interface/mesh_loader.hh
===================================================================
--- /trunk/nv/interface/mesh_loader.hh	(revision 479)
+++ /trunk/nv/interface/mesh_loader.hh	(revision 480)
@@ -32,5 +32,4 @@
 		virtual bool load( stream& source ) = 0;
 		virtual data_channel_set* release_mesh_data( size_t index = 0 ) = 0;
-		virtual mesh_data_pack* release_mesh_data_pack() = 0;
 		virtual size_t get_mesh_count() const = 0;
 		virtual size_t get_nodes_data_count() const { return 0; }
Index: /trunk/src/formats/assimp_loader.cc
===================================================================
--- /trunk/src/formats/assimp_loader.cc	(revision 479)
+++ /trunk/src/formats/assimp_loader.cc	(revision 480)
@@ -462,21 +462,21 @@
 }
 
-mesh_data_pack* nv::assimp_loader::release_mesh_data_pack()
-{
-	if ( m_scene == nullptr || m_mesh_count == 0 ) return nullptr;
-	const aiScene* scene = reinterpret_cast<const aiScene*>( m_scene );
-	bool has_bones = false;
-	data_channel_set* meshes = data_channel_set_creator::create_set_array( m_mesh_count, 2 );
-	for ( size_t m = 0; m < m_mesh_count; ++m )
-	{
-		const aiMesh* mesh = scene->mMeshes[ m ];
-		data_channel_set_creator( &meshes[m] ).set_name( make_name( static_cast<const char*>( mesh->mName.data ) ) );
-		if ( mesh->mNumBones > 0 ) has_bones = true;
-		load_mesh_data(&meshes[m],m);
-	}
-
-	mesh_nodes_data* nodes = ( has_bones ? release_merged_bones( meshes ) : release_mesh_nodes_data(0) );
-	return new mesh_data_pack( m_mesh_count, meshes, nodes );
-}
+// mesh_data_pack* nv::assimp_loader::release_mesh_data_pack()
+// {
+// 	if ( m_scene == nullptr || m_mesh_count == 0 ) return nullptr;
+// 	const aiScene* scene = reinterpret_cast<const aiScene*>( m_scene );
+// 	bool has_bones = false;
+// 	data_channel_set* meshes = data_channel_set_creator::create_set_array( m_mesh_count, 2 );
+// 	for ( size_t m = 0; m < m_mesh_count; ++m )
+// 	{
+// 		const aiMesh* mesh = scene->mMeshes[ m ];
+// 		data_channel_set_creator( &meshes[m] ).set_name( make_name( static_cast<const char*>( mesh->mName.data ) ) );
+// 		if ( mesh->mNumBones > 0 ) has_bones = true;
+// 		load_mesh_data(&meshes[m],m);
+// 	}
+// 
+// 	mesh_nodes_data* nodes = ( has_bones ? release_merged_bones( meshes ) : release_mesh_nodes_data(0) );
+// 	return new mesh_data_pack( m_mesh_count, meshes, nodes );
+// }
 
 nv::size_t nv::assimp_loader::get_nodes_data_count() const
Index: unk/src/formats/md2_loader.cc
===================================================================
--- /trunk/src/formats/md2_loader.cc	(revision 479)
+++ 	(revision )
@@ -1,375 +1,0 @@
-// Copyright (C) 2012-2015 ChaosForge Ltd
-// http://chaosforge.org/
-//
-// This file is part of Nova libraries. 
-// For conditions of distribution and use, see copying.txt file in root folder.
-
-#include "nv/formats/md2_loader.hh"
-
-#include "nv/core/logging.hh"
-#include "nv/interface/data_channel_access.hh"
-
-#include <cstring>
-
-using namespace nv;
-
-// based on http://tfc.duke.free.fr/coding/md2-specs-en.html
-
-// assuming low-endian
-#define MD2_MAX_FRAMES    512
-#define MD2_MAX_SKINS     32
-#define MD2_MAX_VERTS     2048
-#define MD2_MAX_TRIANGLES 4096
-#define MD2_MAX_TEXCOORDS 2048
-#define MD2_NORMAL_COUNT  162
-
-typedef float md2_vec3_t[3];
-
-static const md2_vec3_t md2_normal_table[MD2_NORMAL_COUNT] = {
-#include "src/formats/md2_normals.inc"
-};
-
-struct md2_header_t
-{
-	char magic[4];
-	int version;
-
-	int skinwidth;
-	int skinheight;
-
-	int framesize;
-
-	int num_skins;
-	int num_vertices;
-	int num_st;
-	int num_tris;
-	int num_glcmds;
-	int num_frames;
-
-	int offset_skins;
-	int offset_st;
-	int offset_tris;
-	int offset_frames;
-	int offset_glcmds;
-	int offset_end;
-};
-
-struct md2_skin_t
-{
-	char name[64];
-};
-
-struct md2_texcoord_t
-{
-	short s;
-	short t;
-};
-
-struct md2_triangle_t
-{
-	unsigned short vertex[3];
-	unsigned short st[3];
-};
-
-struct md2_vertex_t
-{
-	uint8 v[3];
-	uint8 n;
-};
-
-struct md2_frame_t
-{
-	md2_vec3_t scale;
-	md2_vec3_t translate;
-	char name[16];
-	md2_vertex_t *vertices;
-};
-
-struct md2_glcmd_t
-{
-	float s;
-	float t;
-	int index;
-};
-
-struct md2_t
-{
-	md2_header_t    header;
-	md2_skin_t*     skins;
-	md2_texcoord_t* texcoords;
-	md2_triangle_t* triangles;
-	md2_frame_t*    frames;
-	int*            glcmds;
-};
-
-static bool check_md2_magic( char* magic )
-{
-	return magic[0] == 'I' && magic[1] == 'D' && magic[2] == 'P' && magic[3] == '2';
-}
-
-static void free_md2_frame( md2_frame_t* frame )
-{
-	delete[] frame->vertices;
-}
-
-static void free_md2( md2_t* md2 )
-{
-	delete[] md2->skins;
-	delete[] md2->texcoords;
-	delete[] md2->triangles;
-	delete[] md2->glcmds;
-	for ( int i = 0; i < md2->header.num_frames; ++i )
-	{
-		free_md2_frame( &(md2->frames[i]) );
-	}
-	delete[] md2->frames;
-}
-
-static bool read_md2_frame( md2_frame_t* frame, unsigned vcount, nv::stream& source )
-{
-	frame->vertices = new md2_vertex_t[ vcount ];
-	source.read( frame->scale,     sizeof(md2_vec3_t), 1 );
-	source.read( frame->translate, sizeof(md2_vec3_t), 1 );
-	source.read( frame->name,      sizeof(char), 16 );
-	source.read( frame->vertices,  sizeof(md2_vertex_t), vcount );
-	return true;
-}
-
-static bool read_md2( md2_t* md2, nv::stream& source )
-{
-	md2->frames     = nullptr;
-	md2->skins      = nullptr;
-	md2->texcoords  = nullptr;
-	md2->triangles  = nullptr;
-	md2->glcmds     = nullptr;
-
-	source.read( &(md2->header), sizeof(md2_header_t), 1 );
-
-	if ( !check_md2_magic( md2->header.magic )       ||
-		md2->header.num_skins    > MD2_MAX_SKINS     ||
-		md2->header.num_vertices > MD2_MAX_VERTS     ||
-		md2->header.num_st       > MD2_MAX_TEXCOORDS ||
-		md2->header.num_tris     > MD2_MAX_TRIANGLES ||
-		md2->header.num_frames   > MD2_MAX_FRAMES )
-	{
-		return false;
-	}
-
-	NV_LOG_INFO( "num_skins    = ", md2->header.num_skins );
-	NV_LOG_INFO( "num_vertices = ", md2->header.num_vertices );
-	NV_LOG_INFO( "num_st       = ", md2->header.num_st );
-	NV_LOG_INFO( "num_tris     = ", md2->header.num_tris );
-	NV_LOG_INFO( "num_frames   = ", md2->header.num_frames );
-
-
-	md2->skins      = new md2_skin_t    [ md2->header.num_skins ];
-	md2->texcoords  = new md2_texcoord_t[ md2->header.num_st ];
-	md2->triangles  = new md2_triangle_t[ md2->header.num_tris ];
-	md2->glcmds     = new int           [ md2->header.num_glcmds ];
-	
-	source.seek( md2->header.offset_skins, origin::SET );
-	source.read( md2->skins, sizeof( md2_skin_t ), static_cast<nv::size_t>( md2->header.num_skins ) );
-
-	source.seek( md2->header.offset_st, origin::SET );
-	source.read( md2->texcoords, sizeof( md2_texcoord_t ), static_cast<nv::size_t>( md2->header.num_st ) );
-
-	source.seek( md2->header.offset_tris, origin::SET );
-	source.read( md2->triangles, sizeof( md2_triangle_t ), static_cast<nv::size_t>( md2->header.num_tris ) );
-
-	source.seek( md2->header.offset_glcmds, origin::SET);
-	source.read( md2->glcmds, sizeof( int ), static_cast<nv::size_t>( md2->header.num_glcmds ) );
-
-	md2->frames    = new md2_frame_t   [ md2->header.num_frames ];
-	source.seek( md2->header.offset_frames, origin::SET );
-	for ( int i = 0; i < md2->header.num_frames; ++i )
-	{
-		if (!read_md2_frame( &(md2->frames[i]), static_cast<unsigned>( md2->header.num_vertices ), source ) ) return false;
-	}
-
-	return true;
-}
-
-static inline vec3 md2_vec3( const md2_vec3_t& v )
-{
-	//	return vec3( v[0], v[1], v[2] );
-	return vec3( v[0], v[2], v[1] );
-}
-
-static vec3 s_md2_normal_cache[MD2_NORMAL_COUNT];
-static bool s_md2_normal_ready = false;
-
-md2_loader::md2_loader( string_table* strings ) 
-	: mesh_loader( strings ), m_md2( nullptr )
-{
-	if ( !s_md2_normal_ready )
-	{
-		for ( int i = 0; i < MD2_NORMAL_COUNT; ++i )
-		{
-			s_md2_normal_cache[i].x = md2_normal_table[i][0];
-//			s_md2_normal_cache[i].y = md2_normal_table[i][1];
-//			s_md2_normal_cache[i].z = md2_normal_table[i][2];
-			s_md2_normal_cache[i].y = md2_normal_table[i][2];
-			s_md2_normal_cache[i].z = md2_normal_table[i][1];
-		}
-	}
-}
-
-
-md2_loader::~md2_loader()
-{
-	md2_t* md2 = reinterpret_cast< md2_t* >( m_md2 );
-	if ( md2 != nullptr)
-	{
-		free_md2( md2 );
-		delete md2;
-	}
-}
-
-bool md2_loader::load( stream& source )
-{
-	md2_t* md2 = new md2_t; 
-	m_md2 = md2;
-	if ( !read_md2( md2, source ) )
-	{
-		return false;
-	}
-	reindex();
-	return true;
-}
-
-nv::size_t md2_loader::get_max_frames() const
-{
-	return static_cast<size_t>( reinterpret_cast<md2_t*>( m_md2 )->header.num_frames );
-}
-
-void nv::md2_loader::reindex()
-{
-	md2_t* md2 = reinterpret_cast< md2_t* >( m_md2 );
-	uint32 num_indexes = static_cast< uint32 >( md2->header.num_tris * 3 );
-
-	uint32 stats_reuse      = 0;
-	uint32 stats_collision  = 0;
-
-	vector< sint32 > index_translation( static_cast< uint32 >( md2->header.num_vertices ), -1 );
-
-	m_new_indexes.clear();
-	m_new_indexes.reserve( num_indexes );
-	m_new_vindexes.reserve( num_indexes );
-	m_new_tindexes.reserve( num_indexes );
-
-	uint16 index_count = 0;
-
-	for ( int i = 0; i < md2->header.num_tris; ++i )
-	{
-		const md2_triangle_t& t = md2->triangles[i];
-		for ( int j = 0; j < 3; ++j )
-		{
-			uint16 index  = t.vertex[j];
-			uint16 tindex = t.st[j];
-
-			if ( index_translation[ index ] != -1 )
-			{
-				uint16 prev = static_cast< uint16 >( index_translation[ index ] );
-				if ( m_new_tindexes[ prev ] == tindex )
-				{
-					m_new_indexes.push_back( prev );
-					stats_reuse++;
-					continue;
-				}
-			}
-			
-			m_new_vindexes.push_back( index );
-			m_new_tindexes.push_back( tindex );
-			m_new_indexes.push_back( index_count );
-			if ( index_translation[ index ] == -1 )
-			{
-				index_translation[ index ] = index_count;
-			}
-			else
-			{
-				stats_collision++;
-			}
-			index_count++;
-		}
-	}
-
-	NV_LOG_INFO( "New vertex count = ", m_new_vindexes.size() );
-	NV_LOG_INFO( "Collisions       = ", stats_collision );
-	NV_LOG_INFO( "Reuse count      = ", stats_reuse );
-}
-
-
-struct vtx_md2_pn
-{
-	nv::vec3 position;
-	nv::vec3 normal;
-};
-
-struct vtx_md2_t
-{
-	nv::vec2 texcoord;
-};
-
-
-data_channel_set* nv::md2_loader::release_mesh_data( size_t )
-{
-	data_channel_set* data = data_channel_set_creator::create_set( 3 );
-	release_mesh_frame( data, -1 );
-	return data;
-}
-
-void nv::md2_loader::release_mesh_frame( data_channel_set* data, sint32 frame )
-{
-	md2_t* md2 = reinterpret_cast< md2_t* >( m_md2 );
-	size_t num_frames = static_cast< size_t >( md2->header.num_frames );
-	size_t num_verts  =	m_new_vindexes.size();
-	size_t current_frame = ( frame == -1 ? 0 : static_cast< size_t >( frame ) );
-	size_t frame_count   = ( frame == -1 ? num_frames : 1 );
-
-	data_channel_set_creator maccess( data );
-	vtx_md2_pn* vtx_pn = maccess.add_channel< vtx_md2_pn >( num_verts * frame_count ).data();
-	vtx_md2_t* vtx_t   = maccess.add_channel< vtx_md2_t >( num_verts ).data();
-	uint16* icp        = &maccess.add_channel< index_u16 >( m_new_indexes.size() ).data()->index;
-
-	maccess.set_name( make_name( "md2_mesh" ) );
-
-	uint32 index = 0;
-	while ( frame_count > 0 )
-	{
-		const md2_frame_t& cframe = md2->frames[current_frame];
-		NV_LOG_INFO( "FrameID = ", cframe.name );
-
-		vec3 scale     = md2_vec3( cframe.scale );
-		vec3 translate = md2_vec3( cframe.translate );
-
-		for (size_t i = 0; i < num_verts; ++i )
-		{
-			const md2_vertex_t& v = cframe.vertices[ m_new_vindexes[ i ] ];
-			vtx_pn[index].position = vec3( v.v[0], v.v[2], v.v[1] ) * scale + translate;
-			vtx_pn[index].normal   = s_md2_normal_cache[ v.n ];
-			index++;
-		}
-		++current_frame;
-		--frame_count;
-	}
-
-	vec2 scale( 1.0f / static_cast<float>( md2->header.skinwidth ), 1.0f / static_cast<float>( md2->header.skinheight ) );
-	for (size_t i = 0; i < num_verts; ++i )
-	{
-		const md2_texcoord_t& st = md2->texcoords[ m_new_tindexes[ i ] ];
-		vtx_t[i].texcoord = scale * vec2( st.s, st.t );
-	}
-
-	if ( m_new_indexes.size() > 0 )
-	{
-		raw_copy_n( m_new_indexes.data(), m_new_indexes.size(), icp );
-	}
-
-}
-
-mesh_data_pack* nv::md2_loader::release_mesh_data_pack()
-{
-	data_channel_set* data = data_channel_set_creator::create_set_array( 1, 3 );
-	release_mesh_frame( &data[0], -1 );
-	return new mesh_data_pack( 1, data );
-}
Index: unk/src/formats/md2_normals.inc
===================================================================
--- /trunk/src/formats/md2_normals.inc	(revision 479)
+++ 	(revision )
@@ -1,162 +1,0 @@
-{ -0.525731f,  0.000000f,  0.850651f }, 
-{ -0.442863f,  0.238856f,  0.864188f }, 
-{ -0.295242f,  0.000000f,  0.955423f }, 
-{ -0.309017f,  0.500000f,  0.809017f }, 
-{ -0.162460f,  0.262866f,  0.951056f }, 
-{  0.000000f,  0.000000f,  1.000000f }, 
-{  0.000000f,  0.850651f,  0.525731f }, 
-{ -0.147621f,  0.716567f,  0.681718f }, 
-{  0.147621f,  0.716567f,  0.681718f }, 
-{  0.000000f,  0.525731f,  0.850651f }, 
-{  0.309017f,  0.500000f,  0.809017f }, 
-{  0.525731f,  0.000000f,  0.850651f }, 
-{  0.295242f,  0.000000f,  0.955423f }, 
-{  0.442863f,  0.238856f,  0.864188f }, 
-{  0.162460f,  0.262866f,  0.951056f }, 
-{ -0.681718f,  0.147621f,  0.716567f }, 
-{ -0.809017f,  0.309017f,  0.500000f }, 
-{ -0.587785f,  0.425325f,  0.688191f }, 
-{ -0.850651f,  0.525731f,  0.000000f }, 
-{ -0.864188f,  0.442863f,  0.238856f }, 
-{ -0.716567f,  0.681718f,  0.147621f }, 
-{ -0.688191f,  0.587785f,  0.425325f }, 
-{ -0.500000f,  0.809017f,  0.309017f }, 
-{ -0.238856f,  0.864188f,  0.442863f }, 
-{ -0.425325f,  0.688191f,  0.587785f }, 
-{ -0.716567f,  0.681718f, -0.147621f }, 
-{ -0.500000f,  0.809017f, -0.309017f }, 
-{ -0.525731f,  0.850651f,  0.000000f }, 
-{  0.000000f,  0.850651f, -0.525731f }, 
-{ -0.238856f,  0.864188f, -0.442863f }, 
-{  0.000000f,  0.955423f, -0.295242f }, 
-{ -0.262866f,  0.951056f, -0.162460f }, 
-{  0.000000f,  1.000000f,  0.000000f }, 
-{  0.000000f,  0.955423f,  0.295242f }, 
-{ -0.262866f,  0.951056f,  0.162460f }, 
-{  0.238856f,  0.864188f,  0.442863f }, 
-{  0.262866f,  0.951056f,  0.162460f }, 
-{  0.500000f,  0.809017f,  0.309017f }, 
-{  0.238856f,  0.864188f, -0.442863f }, 
-{  0.262866f,  0.951056f, -0.162460f }, 
-{  0.500000f,  0.809017f, -0.309017f }, 
-{  0.850651f,  0.525731f,  0.000000f }, 
-{  0.716567f,  0.681718f,  0.147621f }, 
-{  0.716567f,  0.681718f, -0.147621f }, 
-{  0.525731f,  0.850651f,  0.000000f }, 
-{  0.425325f,  0.688191f,  0.587785f }, 
-{  0.864188f,  0.442863f,  0.238856f }, 
-{  0.688191f,  0.587785f,  0.425325f }, 
-{  0.809017f,  0.309017f,  0.500000f }, 
-{  0.681718f,  0.147621f,  0.716567f }, 
-{  0.587785f,  0.425325f,  0.688191f }, 
-{  0.955423f,  0.295242f,  0.000000f }, 
-{  1.000000f,  0.000000f,  0.000000f }, 
-{  0.951056f,  0.162460f,  0.262866f }, 
-{  0.850651f, -0.525731f,  0.000000f }, 
-{  0.955423f, -0.295242f,  0.000000f }, 
-{  0.864188f, -0.442863f,  0.238856f }, 
-{  0.951056f, -0.162460f,  0.262866f }, 
-{  0.809017f, -0.309017f,  0.500000f }, 
-{  0.681718f, -0.147621f,  0.716567f }, 
-{  0.850651f,  0.000000f,  0.525731f }, 
-{  0.864188f,  0.442863f, -0.238856f }, 
-{  0.809017f,  0.309017f, -0.500000f }, 
-{  0.951056f,  0.162460f, -0.262866f }, 
-{  0.525731f,  0.000000f, -0.850651f }, 
-{  0.681718f,  0.147621f, -0.716567f }, 
-{  0.681718f, -0.147621f, -0.716567f }, 
-{  0.850651f,  0.000000f, -0.525731f }, 
-{  0.809017f, -0.309017f, -0.500000f }, 
-{  0.864188f, -0.442863f, -0.238856f }, 
-{  0.951056f, -0.162460f, -0.262866f }, 
-{  0.147621f,  0.716567f, -0.681718f }, 
-{  0.309017f,  0.500000f, -0.809017f }, 
-{  0.425325f,  0.688191f, -0.587785f }, 
-{  0.442863f,  0.238856f, -0.864188f }, 
-{  0.587785f,  0.425325f, -0.688191f }, 
-{  0.688191f,  0.587785f, -0.425325f }, 
-{ -0.147621f,  0.716567f, -0.681718f }, 
-{ -0.309017f,  0.500000f, -0.809017f }, 
-{  0.000000f,  0.525731f, -0.850651f }, 
-{ -0.525731f,  0.000000f, -0.850651f }, 
-{ -0.442863f,  0.238856f, -0.864188f }, 
-{ -0.295242f,  0.000000f, -0.955423f }, 
-{ -0.162460f,  0.262866f, -0.951056f }, 
-{  0.000000f,  0.000000f, -1.000000f }, 
-{  0.295242f,  0.000000f, -0.955423f }, 
-{  0.162460f,  0.262866f, -0.951056f }, 
-{ -0.442863f, -0.238856f, -0.864188f }, 
-{ -0.309017f, -0.500000f, -0.809017f }, 
-{ -0.162460f, -0.262866f, -0.951056f }, 
-{  0.000000f, -0.850651f, -0.525731f }, 
-{ -0.147621f, -0.716567f, -0.681718f }, 
-{  0.147621f, -0.716567f, -0.681718f }, 
-{  0.000000f, -0.525731f, -0.850651f }, 
-{  0.309017f, -0.500000f, -0.809017f }, 
-{  0.442863f, -0.238856f, -0.864188f }, 
-{  0.162460f, -0.262866f, -0.951056f }, 
-{  0.238856f, -0.864188f, -0.442863f }, 
-{  0.500000f, -0.809017f, -0.309017f }, 
-{  0.425325f, -0.688191f, -0.587785f }, 
-{  0.716567f, -0.681718f, -0.147621f }, 
-{  0.688191f, -0.587785f, -0.425325f }, 
-{  0.587785f, -0.425325f, -0.688191f }, 
-{  0.000000f, -0.955423f, -0.295242f }, 
-{  0.000000f, -1.000000f,  0.000000f }, 
-{  0.262866f, -0.951056f, -0.162460f }, 
-{  0.000000f, -0.850651f,  0.525731f }, 
-{  0.000000f, -0.955423f,  0.295242f }, 
-{  0.238856f, -0.864188f,  0.442863f }, 
-{  0.262866f, -0.951056f,  0.162460f }, 
-{  0.500000f, -0.809017f,  0.309017f }, 
-{  0.716567f, -0.681718f,  0.147621f }, 
-{  0.525731f, -0.850651f,  0.000000f }, 
-{ -0.238856f, -0.864188f, -0.442863f }, 
-{ -0.500000f, -0.809017f, -0.309017f }, 
-{ -0.262866f, -0.951056f, -0.162460f }, 
-{ -0.850651f, -0.525731f,  0.000000f }, 
-{ -0.716567f, -0.681718f, -0.147621f }, 
-{ -0.716567f, -0.681718f,  0.147621f }, 
-{ -0.525731f, -0.850651f,  0.000000f }, 
-{ -0.500000f, -0.809017f,  0.309017f }, 
-{ -0.238856f, -0.864188f,  0.442863f }, 
-{ -0.262866f, -0.951056f,  0.162460f }, 
-{ -0.864188f, -0.442863f,  0.238856f }, 
-{ -0.809017f, -0.309017f,  0.500000f }, 
-{ -0.688191f, -0.587785f,  0.425325f }, 
-{ -0.681718f, -0.147621f,  0.716567f }, 
-{ -0.442863f, -0.238856f,  0.864188f }, 
-{ -0.587785f, -0.425325f,  0.688191f }, 
-{ -0.309017f, -0.500000f,  0.809017f }, 
-{ -0.147621f, -0.716567f,  0.681718f }, 
-{ -0.425325f, -0.688191f,  0.587785f }, 
-{ -0.162460f, -0.262866f,  0.951056f }, 
-{  0.442863f, -0.238856f,  0.864188f }, 
-{  0.162460f, -0.262866f,  0.951056f }, 
-{  0.309017f, -0.500000f,  0.809017f }, 
-{  0.147621f, -0.716567f,  0.681718f }, 
-{  0.000000f, -0.525731f,  0.850651f }, 
-{  0.425325f, -0.688191f,  0.587785f }, 
-{  0.587785f, -0.425325f,  0.688191f }, 
-{  0.688191f, -0.587785f,  0.425325f }, 
-{ -0.955423f,  0.295242f,  0.000000f }, 
-{ -0.951056f,  0.162460f,  0.262866f }, 
-{ -1.000000f,  0.000000f,  0.000000f }, 
-{ -0.850651f,  0.000000f,  0.525731f }, 
-{ -0.955423f, -0.295242f,  0.000000f }, 
-{ -0.951056f, -0.162460f,  0.262866f }, 
-{ -0.864188f,  0.442863f, -0.238856f }, 
-{ -0.951056f,  0.162460f, -0.262866f }, 
-{ -0.809017f,  0.309017f, -0.500000f }, 
-{ -0.864188f, -0.442863f, -0.238856f }, 
-{ -0.951056f, -0.162460f, -0.262866f }, 
-{ -0.809017f, -0.309017f, -0.500000f }, 
-{ -0.681718f,  0.147621f, -0.716567f }, 
-{ -0.681718f, -0.147621f, -0.716567f }, 
-{ -0.850651f,  0.000000f, -0.525731f }, 
-{ -0.688191f,  0.587785f, -0.425325f }, 
-{ -0.587785f,  0.425325f, -0.688191f }, 
-{ -0.425325f,  0.688191f, -0.587785f }, 
-{ -0.425325f, -0.688191f, -0.587785f }, 
-{ -0.587785f, -0.425325f, -0.688191f }, 
-{ -0.688191f, -0.587785f, -0.425325f }
Index: unk/src/formats/md3_loader.cc
===================================================================
--- /trunk/src/formats/md3_loader.cc	(revision 479)
+++ 	(revision )
@@ -1,463 +1,0 @@
-// Copyright (C) 2012-2015 ChaosForge Ltd
-// http://chaosforge.org/
-//
-// This file is part of Nova libraries. 
-// For conditions of distribution and use, see copying.txt file in root folder.
-
-#include "nv/formats/md3_loader.hh"
-
-#include "nv/core/logging.hh"
-#include "nv/interface/data_channel_access.hh"
-
-using namespace nv;
-
-// based on http://www.icculus.org/~phaethon/q3/formats/md3format.html#Surface
-
-// assuming low-endian
-#define MD3_MAX_FRAMES    1024
-#define MD3_MAX_TAGS      16
-#define MD3_MAX_SURFACES  32
-#define MD3_MAX_SHADERS   256
-#define MD3_MAX_VERTS     4096
-#define MD3_MAX_TRIANGLES 8192
-#define MD3_XYZ_SCALE     (1.0f/64.0f)
-
-struct md3_vec3_t
-{
-	float xyz[3];
-};
-
-struct md3_header_t
-{
-	char   ident[4]; // IDP3
-	sint32 version;  // 15
-	uint8  name[64]; // path name
-	sint32 flags;
-	sint32 num_frames;     // Number of Frame objects, with a maximum of MD3_MAX_FRAMES. Current value of MD3_MAX_FRAMES is 1024.
-	sint32 num_tags;       // Number of Tag objects, with a maximum of MD3_MAX_TAGS. Current value of MD3_MAX_TAGS is 16.
-	sint32 num_surfaces;   // Number of Surface objects, with a maximum of MD3_MAX_SURFACES. Current value of MD3_MAX_SURFACES is 32.
-	sint32 num_skins;      // Number of Skin objects. I should note that I have not seen an MD3 using this particular field for anything; this appears to be an artifact from the Quake 2 MD2 format. Surface objects have their own Shader field.
-	sint32 ofs_frames;     // Relative offset from start of MD3 object where Frame objects start. The Frame objects are written sequentially, that is, when you read one Frame object, you do not need to seek() for the next object.
-	sint32 ofs_tags;       // Relative offset from start of MD3 where Tag objects start. Similarly written sequentially.
-	sint32 ofs_surfaces;   // Relative offset from start of MD3 where Surface objects start. Again, written sequentially.
-	sint32 ofs_eof;        // Relative offset from start of MD3 to the end of the MD3 object. Note there is no offset for Skin objects.
-};
-
-struct md3_frame_t
-{
-	md3_vec3_t min_bounds;
-	md3_vec3_t max_bounds;
-	md3_vec3_t local_origin;
-	float      radius;
-	uint8      name[16];
-};
-
-struct md3_tag_t
-{
-	uint8      name[64];
-	md3_vec3_t origin;
-	md3_vec3_t axis[3];
-};
-
-struct md3_surface_header_t
-{
-	char   ident[4]; // IDP3
-	uint8  name[64]; // path name
-	sint32 flags;
-	sint32 num_frames;
-	sint32 num_shaders;
-	sint32 num_verts;
-	sint32 num_triangles;
-	sint32 ofs_triangles;
-	sint32 ofs_shaders;
-	sint32 ofs_st;
-	sint32 ofs_xyznormal;
-	sint32 ofs_end;
-};
-
-struct md3_shader_t
-{
-	uint8  name[64]; 
-	sint32 shader_index;
-};
-
-struct md3_triangle_t
-{
-	sint32 indexes[3];
-};
-
-struct md3_texcoord_t
-{
-	float  st[2];
-};
-
-struct md3_vertex_t
-{
-	sint16 x;
-	sint16 y;
-	sint16 z;
-	uint16 normal;
-};
-
-struct md3_surface_t
-{
-	md3_surface_header_t header;
-	md3_shader_t*        shaders;
-	md3_triangle_t*      triangles;
-	md3_texcoord_t*      st;
-	md3_vertex_t*        vertices;
-};
-
-struct md3_t
-{
-	md3_header_t   header;
-	md3_frame_t*   frames;
-	md3_tag_t*     tags;
-	md3_surface_t* surfaces;
-	// extra information (not in md3 file)
-	sint32         vertices_per_frame;
-};
-
-static bool check_md3_magic( char* magic )
-{
-	return magic[0] == 'I' && magic[1] == 'D' && magic[2] == 'P' && magic[3] == '3';
-}
-
-static void free_md3_surface( md3_surface_t * surface )
-{
-	delete[] surface->shaders;
-	delete[] surface->triangles;
-	delete[] surface->st;
-	delete[] surface->vertices;
-}
-
-static void free_md3( md3_t * md3 )
-{
-	sint32 count = md3->header.num_surfaces;
-	for ( sint32 i = 0; i < count; ++i )
-	{
-		free_md3_surface( &md3->surfaces[i] );
-	}
-	delete[] md3->frames;
-	delete[] md3->tags;
-	delete[] md3->surfaces;
-}
-
-static bool read_surface( md3_surface_t * surface, nv::stream& source )
-{
-	sint32 pos = static_cast< sint32 >( source.tell() );
-	source.read( &surface->header, sizeof(md3_surface_header_t), 1 );
-
-	if ( !check_md3_magic( surface->header.ident ) )          return false;
-	if ( surface->header.num_frames    >  MD3_MAX_FRAMES )    return false;
-	if ( surface->header.num_shaders   >  MD3_MAX_SHADERS )   return false;
-	if ( surface->header.num_verts     >  MD3_MAX_VERTS )     return false;
-	if ( surface->header.num_triangles >  MD3_MAX_TRIANGLES ) return false;
-
-	surface->shaders   = new md3_shader_t  [ surface->header.num_shaders ];
-	surface->vertices  = new md3_vertex_t  [ surface->header.num_verts * surface->header.num_frames ];
-	surface->st        = new md3_texcoord_t[ surface->header.num_verts ];
-	surface->triangles = new md3_triangle_t[ surface->header.num_triangles ];
-
-	source.seek( pos + surface->header.ofs_shaders, origin::SET );
-	source.read( surface->shaders, sizeof( md3_shader_t ), static_cast<nv::size_t>( surface->header.num_shaders ) );
-
-	source.seek( pos + surface->header.ofs_triangles, origin::SET );
-	source.read( surface->triangles, sizeof( md3_triangle_t ), static_cast<nv::size_t>( surface->header.num_triangles ) );
-
-	source.seek( pos + surface->header.ofs_st, origin::SET );
-	source.read( surface->st, sizeof( md3_texcoord_t ), static_cast<nv::size_t>( surface->header.num_verts ) );
-
-	source.seek( pos + surface->header.ofs_xyznormal, origin::SET );
-	source.read( surface->vertices, sizeof( md3_vertex_t ), static_cast<nv::size_t>( surface->header.num_verts * surface->header.num_frames ) );
-
-	if ( source.tell() != static_cast<nv::size_t>( pos + surface->header.ofs_end ) ) return false;
-
-	return true;
-}
-
-static bool read_md3( md3_t * md3, nv::stream& source )
-{
-	md3->frames   = nullptr;
-	md3->tags     = nullptr;
-	md3->surfaces = nullptr;
-
-	source.read( &md3->header, sizeof(md3_header_t), 1 );
-
-	if ( !check_md3_magic( md3->header.ident ) )        return false;
-	if ( md3->header.num_frames   >  MD3_MAX_FRAMES )   return false;
-	if ( md3->header.num_tags     >  MD3_MAX_TAGS )     return false;
-	if ( md3->header.num_surfaces >  MD3_MAX_SURFACES ) 
-	{
-		// to always have a safe free
-		md3->header.num_surfaces = 0;
-		return false;
-	}
-
-	md3->frames   = new md3_frame_t  [ md3->header.num_frames ];
-	md3->tags     = new md3_tag_t    [ md3->header.num_tags * md3->header.num_frames ];
-	md3->surfaces = new md3_surface_t[ md3->header.num_surfaces ];
-	nv::raw_zero_n( md3->surfaces, static_cast< nv::size_t >( md3->header.num_surfaces ) );
-
-	source.seek( md3->header.ofs_frames, origin::SET );
-	source.read( md3->frames, sizeof( md3_frame_t ), static_cast<nv::size_t>( md3->header.num_frames ) );
-
-	if ( md3->header.num_tags > 0 )
-	{
-		source.seek( md3->header.ofs_tags, origin::SET );
-		source.read( md3->tags, sizeof( md3_tag_t ), static_cast<nv::size_t>( md3->header.num_tags * md3->header.num_frames ) );
-	}
-
-	source.seek( md3->header.ofs_surfaces, origin::SET );
-	md3->vertices_per_frame = 0;
-
-	for ( sint32 i = 0; i < md3->header.num_surfaces; ++i )
-	{
-		if ( !read_surface( md3->surfaces + i, source ) ) return false;
-		if ( md3->header.num_frames != md3->surfaces[i].header.num_frames ) return false;
-		md3->vertices_per_frame += md3->surfaces[i].header.num_verts;
-	}
-	return true;
-}
-
-static inline vec3 md3_vec3( const md3_vec3_t& v )
-{
-//	return vec3( v.xyz[0], v.xyz[1], v.xyz[2] );
-	return vec3( v.xyz[0], v.xyz[2], v.xyz[1] );
-}
-
-static inline vec2 md3_texcoord( const md3_texcoord_t& v )
-{
-	return vec2( v.st[0], v.st[1] );
-}
-
-static vec3 s_normal_cache[256*256];
-static bool s_normal_ready = false;
-
-md3_loader::md3_loader( string_table* strings, bool merge_all )
-	: mesh_loader( strings ), m_merge_all( merge_all ), m_md3( nullptr )
-{
-	if ( !s_normal_ready )
-	{
-		float pi      = math::pi<float>();
-		float convert = (2 * pi) / 255.0f;
-		int n = 0;
-		for ( int lat = 0; lat < 256; ++lat )
-		{
-			float flat    = lat * convert;
-			float sin_lat = nv::sin( flat );
-			float cos_lat = nv::cos( flat );
-			for ( int lng = 0; lng < 256; ++lng, ++n )
-			{
-				float flng    = lng * convert;
-				float sin_lng = nv::sin( flng );
-				float cos_lng = nv::cos( flng );
-				s_normal_cache[n].x = cos_lat * sin_lng;
-//				s_normal_cache[n].y = sin_lat * sin_lng;
-//				s_normal_cache[n].z = cos_lng;
-				s_normal_cache[n].z = sin_lat * sin_lng;
-				s_normal_cache[n].y = cos_lng;
-			}
-		}
-
-		s_normal_ready = true;
-	}
-}
-
-
-nv::md3_loader::~md3_loader()
-{
-	md3_t* md3 = reinterpret_cast< md3_t* >( m_md3 );
-	if ( md3 != nullptr )
-	{
-		free_md3( md3 );
-		delete md3;
-	}
-}
-
-bool nv::md3_loader::load( stream& source )
-{
-	md3_t* md3 = new md3_t;
-	m_md3 = md3;
-	if ( !read_md3( md3, source ) )
-	{
-		return false;
-	}
-	return true;
-}
-
-void nv::md3_loader::load_tags( raw_data_channel* channel, const string_view& tag )
-{
-	md3_t* md3 = reinterpret_cast< md3_t* >( m_md3 );
-	data_channel_access< md3_key > access( channel );
-	// TODO: is this brain damaged in efficiency (loop nest order) or what?
-	for ( sint32 f = 0; f < md3->header.num_frames; ++f )
-	{
-		for ( sint32 i = 0; i < md3->header.num_tags; ++i )
-		{
-			const md3_tag_t& rtag = md3->tags[i + md3->header.num_tags * f];
-			string_view rname( reinterpret_cast< const char* >(rtag.name) );
-			if (rname == tag)
-			{
-				vec3 axisx  ( md3_vec3( rtag.axis[0] ) );
-				vec3 axisz  ( md3_vec3( rtag.axis[1] ) );
-				vec3 axisy  ( md3_vec3( rtag.axis[2] ) );
-				vec3 origin ( md3_vec3( rtag.origin )  );
-				access.data()[f].tform = transform( origin, quat( mat3( axisx, axisy, axisz ) ) );
-			}
-		}
-
-	}
-}
-
-struct vtx_md3_pn
-{
-	nv::vec3 position;
-	nv::vec3 normal;
-};
-
-struct vtx_md3_t
-{
-	nv::vec2 texcoord;
-};
-
-data_channel_set* nv::md3_loader::release_mesh_data( nv::size_t index )
-{
-	data_channel_set* data = data_channel_set_creator::create_set(3);
-	release_mesh_frame( data, -1, static_cast< sint32 >( index ) );
-	return data;
-}
-
-void nv::md3_loader::release_mesh_frame( data_channel_set* data, sint32 frame, sint32 surface )
-{
-	md3_t* md3 = reinterpret_cast< md3_t* >( m_md3 );
-	sint32 num_surfaces  = md3->header.num_surfaces;
-	sint32 num_verts     = 0;
-	sint32 current_frame = ( frame == -1 ? 0 : frame );
-	sint32 frame_count   = ( frame == -1 ? md3->header.num_frames : 1 );
-	sint32 current_surf  = ( surface == -1 ? 0 : surface );
-	sint32 surf_count    = ( surface == -1 ? md3->header.num_surfaces : 1 );
-	sint32 index_count   = 0;
-
-	if ( surface >= 0 )
-	{
-		index_count = md3->surfaces[surface].header.num_triangles * 3;
-		num_verts   = md3->surfaces[surface].header.num_verts;
-	}
-	else
-		for ( sint32 i = 0; i < num_surfaces; ++i )
-		{
-			index_count += md3->surfaces[i].header.num_triangles * 3;
-			num_verts   += md3->surfaces[i].header.num_verts;
-		}
-
-	data_channel_set_creator maccess( data );
-	maccess.set_name( make_name( reinterpret_cast<char*>( md3->header.name ) ) );
-
-
-	vtx_md3_pn* vtx_pn = maccess.add_channel< vtx_md3_pn >( static_cast< uint32 >( num_verts * frame_count ) ).data();
-	vtx_md3_t*  vtx_t  = maccess.add_channel< vtx_md3_t >( static_cast< uint32 >( num_verts ) ).data();
-	uint16*     icp    = reinterpret_cast< uint16* >( maccess.add_channel< index_u16 >( static_cast< uint32 >( index_count ) ).data() );
-
-	uint32 index  = 0;
-	uint32 iindex = 0;
-	sint32 index_base = 0;
-
-	while ( surf_count > 0 )
-	{
-		const md3_surface_t& sface  = md3->surfaces[ current_surf ];
-		const uint32         vcount = static_cast< uint32 >( sface.header.num_verts );
-		const uint32         tcount = static_cast< uint32 >( sface.header.num_triangles );
-
-		for (uint32 j = 0; j < vcount; ++j )
-		{
-			vtx_t[index++].texcoord = md3_texcoord( sface.st[j] );
-		}
-
-		for (size_t j = 0; j < tcount; ++j )
-		{
-			const md3_triangle_t& t = sface.triangles[j];
-			icp[iindex++] = static_cast< uint16 >( index_base + t.indexes[0] );
-			icp[iindex++] = static_cast< uint16 >( index_base + t.indexes[1] );
-			icp[iindex++] = static_cast< uint16 >( index_base + t.indexes[2] );
-		}
-		index_base += sface.header.num_verts;
-		++current_surf;
-		--surf_count;
-	}
-
-	index = 0;
-	while ( frame_count > 0 )
-	{
-		current_surf  = ( surface == -1 ? 0 : surface );
-		surf_count    = ( surface == -1 ? md3->header.num_surfaces : 1 );
-
-		while ( surf_count > 0 )
-		{
-			md3_surface_t& sface  = md3->surfaces[current_surf];
-			sint32 vcount = sface.header.num_verts;
-			sint32 offset = vcount * current_frame;
-			sint32 limit  = vcount + offset;
-			for ( sint32 j = offset; j < limit; ++j )
-			{
-				md3_vertex_t& v = sface.vertices[j];
-				vtx_pn[index].position = vec3( v.x * MD3_XYZ_SCALE, v.z * MD3_XYZ_SCALE, v.y * MD3_XYZ_SCALE );
-				vtx_pn[index].normal   = s_normal_cache[ v.normal ];
-				index++;
-			}
-			++current_surf;
-			--surf_count;
-		}
-		++current_frame;
-		--frame_count;
-	}
-
-}
-
-mesh_nodes_data* nv::md3_loader::release_mesh_nodes_data( nv::size_t )
-{
-	md3_t* md3 = reinterpret_cast< md3_t* >( m_md3 );
-	uint32 node_count = uint32( md3->header.num_tags );
-	if ( node_count == 0 ) return nullptr;
-	mesh_nodes_data* result = new mesh_nodes_data( m_strings ? m_strings->insert( "tags" ) : shash64() );
-	for ( uint32 i = 0; i < node_count; ++i )
-	{
-		const md3_tag_t& rtag = md3->tags[i];
-		string_view name( reinterpret_cast< const char* >(rtag.name) );
-		data_channel_set* set = data_channel_set_creator::create_set( 1 );
-		data_channel_set_creator access( set );
-		access.set_name( make_name( name ) );
-		load_tags( access.add_channel<md3_key>( uint32( md3->header.num_frames ) ).channel(), name );
-		result->append( set );
-	}
-	result->initialize();
-	return result;
-}
-
-mesh_data_pack* nv::md3_loader::release_mesh_data_pack()
-{
-	md3_t* md3 = reinterpret_cast<md3_t*>( m_md3 );
-	int count = 1;
-	data_channel_set* data = nullptr;
-	if ( m_merge_all )
-	{
-		data = data_channel_set_creator::create_set_array(1,3);
-		release_mesh_frame( &data[0], -1, -1 );
-	}
-	else
-	{
-		count = md3->header.num_surfaces;
-		data = data_channel_set_creator::create_set_array( count, 3 );
-		for ( int i = 0; i < count; ++i )
-		{
-			release_mesh_frame( &data[i], -1, i );
-			data_channel_set_creator( &data[i] ).set_name( make_name( reinterpret_cast< char* >( md3->surfaces[i].header.name ) ) );
-		}
-	}
-	return new mesh_data_pack( uint32( count ), data, release_mesh_nodes_data() );
-}
-
-nv::size_t md3_loader::get_max_frames() const
-{
-	return static_cast<size_t>( reinterpret_cast<md3_t*>( m_md3 )->header.num_frames );
-}
Index: /trunk/src/formats/nmd_loader.cc
===================================================================
--- /trunk/src/formats/nmd_loader.cc	(revision 479)
+++ /trunk/src/formats/nmd_loader.cc	(revision 480)
@@ -50,17 +50,4 @@
 }
 
-mesh_data_pack* nv::nmd_loader::release_mesh_data_pack()
-{
-	uint32 size = m_meshes.size();
-	data_channel_set* meshes = data_channel_set_creator::create_set_array( size, 0 );
-	for ( uint32 i = 0; i < size; ++i )
-	{
-		meshes[i] = move( *m_meshes[i] );
-		delete m_meshes[i];
-	}
-	m_meshes.clear();
-	return new mesh_data_pack( size, meshes, release_mesh_nodes_data() );
-}
-
 void nv::nmd_loader::reset()
 {
@@ -240,19 +227,20 @@
 }
 
-void nv::nmd_dump( stream& stream_out, const mesh_data_pack* model, const string_table* strings, uint64 name )
+void nv::nmd_dump( stream& stream_out, array_view< data_channel_set* > meshes, const mesh_nodes_data* nodes, const string_table* strings /*= nullptr*/, uint64 name /*= 0 */ )
 {
 	uint32 elements = ( strings ? 1 : 0 ) // +1 string array
-		+ model->get_count() // meshes
-		+ ( model->get_node_count() > 0 ? 1 : 0 ); // nodes
+		+ meshes.size() // meshes
+		+ ( nodes && nodes->size() > 0 ? 1 : 0 ); // nodes
 	nmd_dump_header( stream_out, elements, name );
 
-	for ( uint32 i = 0; i < model->get_count(); ++i )
-	{
-		nmd_dump_element( stream_out, *model->get_mesh( i ), nv::nmd_type::MESH );
-	}
-
-	if ( model->get_node_count() > 0 )
-	{
-		nmd_dump_nodes( stream_out, *model->get_nodes() );
+	for ( uint32 i = 0; i < meshes.size(); ++i )
+	{
+		NV_ASSERT( meshes[i], "mesh is null!" );
+		nmd_dump_element( stream_out, *meshes[i], nv::nmd_type::MESH );
+	}
+
+	if ( nodes && nodes->size() > 0 )
+	{
+		nmd_dump_nodes( stream_out, *nodes );
 	}
 
@@ -262,2 +250,19 @@
 	}
 }
+
+void nv::nmd_dump( stream& stream_out, const mesh_nodes_data& animation, const string_table* strings, uint64 name )
+{
+	uint32 elements = ( strings ? 1 : 0 ) // +1 string array
+		+ ( animation.size() > 0 ? 1 : 0 ); // nodes
+	nmd_dump_header( stream_out, elements, name );
+
+	if ( animation.size() > 0 )
+	{
+		nmd_dump_nodes( stream_out, animation );
+	}
+
+	if ( strings )
+	{
+		nmd_dump_strings( stream_out, *strings );
+	}
+}
Index: /trunk/src/formats/obj_loader.cc
===================================================================
--- /trunk/src/formats/obj_loader.cc	(revision 479)
+++ /trunk/src/formats/obj_loader.cc	(revision 480)
@@ -357,14 +357,2 @@
 }
 
-mesh_data_pack* nv::obj_loader::release_mesh_data_pack()
-{
-	uint32 size = m_meshes.size();
-	data_channel_set* meshes = data_channel_set_creator::create_set_array( size, 1 );
-	for ( uint32 i = 0; i < size; ++i )
-	{
-		meshes[i] = move( *m_meshes[i] );
-		delete m_meshes[i];
-	}
-	m_meshes.clear();
-	return new mesh_data_pack( size, meshes );
-}
Index: unk/src/gfx/keyframed_mesh.cc
===================================================================
--- /trunk/src/gfx/keyframed_mesh.cc	(revision 479)
+++ 	(revision )
@@ -1,248 +1,0 @@
-// Copyright (C) 2011-2015 ChaosForge Ltd
-// http://chaosforge.org/
-//
-// This file is part of Nova libraries. 
-// For conditions of distribution and use, see copying.txt file in root folder.
-
-#include "nv/gfx/keyframed_mesh.hh"
-
-#include "nv/interface/context.hh"
-#include "nv/interface/device.hh"
-#include "nv/core/logging.hh"
-
-using namespace nv;
-
-nv::keyframed_mesh::keyframed_mesh( context* a_context, const data_channel_set* a_data, const mesh_nodes_data* a_tag_map )
-	: animated_mesh()
-	, m_context( a_context )
-	, m_tag_map( a_tag_map )
-	, m_last_frame( 0 )
-	, m_next_frame( 0 )
-	, m_interpolation( 0.0f )
-	, m_active( false )
-{
-	m_index_count   = a_data->get_channel_size( slot::INDEX );
-	m_vertex_count  = a_data->get_channel_size<vertex_t>();
-	uint32 pos_size = a_data->get_channel_size<vertex_pnt>();
-	if ( pos_size == 0 )
-	{
-		pos_size      = a_data->get_channel_size<vertex_pn>();
-		m_has_tangent = false;
-		m_vsize       = sizeof( vertex_pn );
-	}
-	else
-	{
-		m_has_tangent = true;
-		m_vsize       = sizeof( vertex_pnt );
-	}
-	m_frame_count  = pos_size / m_vertex_count;
-	m_pbuffer      = buffer();
-
-	if ( m_tag_map && m_tag_map->size() > 0 )
-	{
-		m_interpolation_key = (*m_tag_map)[ 0 ]->get_interpolation_key();
-	}
-}
-
-nv::size_t keyframed_mesh::get_max_frames() const
-{
-	return m_frame_count;
-}
-
-transform keyframed_mesh::get_node_transform( uint32 node_id ) const
-{
-	if ( !m_tag_map ) return transform();
-	NV_ASSERT( node_id < m_tag_map->size(), "TAGMAP FAIL" );
-	NV_ASSERT( (*m_tag_map)[node_id]->size() > 0, "TAG FAIL" );
-	raw_channel_interpolator interpolator( ( *m_tag_map )[node_id], m_interpolation_key );
-	return interpolator.get< transform >( m_last_frame, m_next_frame, m_interpolation );
-}
-
-mat4 keyframed_mesh::get_node_matrix( uint32 node_id ) const
-{
-	return get_node_transform( node_id ).extract();
-}
-
-void nv::keyframed_mesh::set_frame( uint32 frame )
-{
-	m_last_frame    = frame;
-	m_next_frame    = frame;
-	m_active        = false;
-	m_interpolation = 0.0f;
-}
-
-void nv::keyframed_mesh::update_animation( animation_entry* anim, uint32 a_ms_anim_time )
-{
-	if ( m_active )
-	{
-		float  fframe   = ( static_cast<float>( a_ms_anim_time ) * 0.001f ) * anim->get_fps();
-		uint32 frame    = uint32( fframe );
-		float  reminder = fframe - static_cast<float>( frame );
-		uint32 duration = anim->is_looping() ? anim->get_frame_count() + 1 : anim->get_frame_count();
-
-		if ( frame >= duration )
-		{
-			if ( anim->is_looping() )
-			{
-				frame  = frame % duration;
-				fframe = static_cast<float>( frame ) + reminder;
-			}
-			else
-			{
-				m_active        = false;
-				m_last_frame    = anim->get_end_frame();
-				m_next_frame    = m_last_frame;
-				m_interpolation = 0.0f;
-				return;
-			}
-		}
-		m_last_frame    = frame + anim->get_start_frame();
-		m_next_frame    = m_last_frame + 1;
-		if ( m_next_frame > anim->get_end_frame() ) m_next_frame = anim->get_start_frame();
-		m_interpolation = reminder;
-	}
-}
-
-
-void nv::keyframed_mesh::update( program a_program )
-{
-	m_context->get_device()->set_opt_uniform( a_program, "nv_interpolate", m_interpolation );
-}
-
-nv::keyframed_mesh::~keyframed_mesh()
-{
-	m_context->release( m_va );
-}
-
-void nv::keyframed_mesh::run_animation( animation_entry* a_anim )
-{
-	if ( a_anim )
-	{
-		m_active        = true;
-		m_last_frame    = 0;
-		m_next_frame    = 0;
-		m_interpolation = 0.0f;
-	}
-	else
-	{
-		m_active = false;
-	}
-}
-
-nv::keyframed_mesh_gpu::keyframed_mesh_gpu( context* a_context, const data_channel_set* a_data, const mesh_nodes_data* a_tag_map )
-	: keyframed_mesh( a_context, a_data, a_tag_map )
-	, m_loc_next_position( -1 )
-	, m_loc_next_normal( -1 )
-	, m_loc_next_tangent( -1 )
-	, m_gpu_last_frame( 0xFFFFFFFF )
-	, m_gpu_next_frame( 0xFFFFFFFF )
-{
-	m_va      = a_context->create_vertex_array( a_data, STATIC_DRAW );
-	m_pbuffer = a_context->find_buffer( m_va, slot::POSITION );
-}
-
-
-void nv::keyframed_mesh_gpu::update_animation( animation_entry* anim, uint32 a_anim_time )
-{
-	keyframed_mesh::update_animation( anim, a_anim_time );
-	if ( m_loc_next_position == -1 ) return;
-	if ( m_gpu_last_frame != m_last_frame )
-	{
-		uint32 base_offset = m_last_frame * m_vertex_count * m_vsize; 
-		m_context->update_attribute_offset( m_va, slot::POSITION, base_offset );
-		m_context->update_attribute_offset( m_va, slot::NORMAL,   base_offset + sizeof( vec3 ) );
-		if ( m_has_tangent && m_loc_next_tangent != -1 )
-		{
-			m_context->update_attribute_offset( m_va, slot::TANGENT, base_offset + 2*sizeof( vec3 ) );
-		}
-		m_gpu_last_frame = m_last_frame;
-	}
-	if ( m_loc_next_position != -1 && m_gpu_next_frame != m_next_frame )
-	{
-		uint32 base_offset = m_next_frame * m_vertex_count * m_vsize; 
-		m_context->update_attribute_offset( m_va, static_cast<slot>( m_loc_next_position ), base_offset );
-		m_context->update_attribute_offset( m_va, static_cast<slot>( m_loc_next_normal ), base_offset + sizeof( vec3 ) );
-		if ( m_has_tangent && m_loc_next_tangent != -1 )
-		{
-			m_context->update_attribute_offset( m_va, static_cast<slot>( m_loc_next_tangent ), base_offset + 2*sizeof( vec3 ) );
-		}
-		m_gpu_next_frame = m_next_frame;
-	}
-}
-
-void nv::keyframed_mesh_gpu::update( program a_program )
-{
-	if ( m_loc_next_position == -1 )
-	{
-		device* dev = m_context->get_device();
-		m_loc_next_position = dev->get_attribute_location( a_program, "nv_next_position" );
-		m_loc_next_normal   = dev->get_attribute_location( a_program, "nv_next_normal" );
-		if ( m_has_tangent )
-			m_loc_next_tangent  = dev->get_attribute_location( a_program, "nv_next_tangent" );
-
-		m_context->add_vertex_buffer( m_va, static_cast<slot>( m_loc_next_position ), m_pbuffer, FLOAT, 3, 0, m_vsize, false );
-		m_context->add_vertex_buffer( m_va, static_cast<slot>( m_loc_next_normal ),   m_pbuffer, FLOAT, 3, sizeof( vec3 ), m_vsize, false );
-		if ( m_has_tangent )
-			m_context->add_vertex_buffer( m_va, static_cast<slot>( m_loc_next_tangent ), m_pbuffer, FLOAT, 4, 2*sizeof( vec3 ), m_vsize, false );
-	}
-	keyframed_mesh::update( a_program );
-}
-
-nv::keyframed_mesh_cpu::keyframed_mesh_cpu( context* a_context, const data_channel_set* a_data, const mesh_nodes_data* a_tag_map )
-	: keyframed_mesh( a_context, a_data, a_tag_map )
-{
-	const raw_data_channel* vchannel = m_has_tangent ? a_data->get_channel< vertex_pnt >() : a_data->get_channel< vertex_pn >();
-	m_va      = m_context->create_vertex_array();
-	m_pbuffer = m_context->get_device()->create_buffer( VERTEX_BUFFER, STATIC_DRAW, m_vertex_count * m_vsize, vchannel->raw_data() );
-	m_context->add_vertex_buffers( m_va, m_pbuffer, vchannel->descriptor() );
-
-	buffer  vb = m_context->get_device()->create_buffer( VERTEX_BUFFER, STATIC_DRAW, m_vertex_count * sizeof( vec2 ), a_data->get_channel_data<vertex_t>() );
-	m_context->add_vertex_buffers( m_va, vb, a_data->get_channel<vertex_t>()->descriptor() );
-
-	const raw_data_channel* index_channel = a_data->get_channel( slot::INDEX );
-	buffer  ib = m_context->get_device()->create_buffer( INDEX_BUFFER, STATIC_DRAW, index_channel->raw_size(), index_channel->raw_data() );
-	m_context->set_index_buffer( m_va, ib, index_channel->descriptor()[0].etype, true );
-
-	m_data = new uint8[ m_vertex_count * m_vsize ];
-
-	m_model_data = vchannel->raw_data();
-}
-
-void nv::keyframed_mesh_cpu::update_animation( animation_entry* anim, uint32 a_anim_time )
-{
-	keyframed_mesh::update_animation( anim, a_anim_time );
-	// TODO: this could be done generic for any data
-	if ( m_has_tangent )
-	{
-		const vertex_pnt* data = reinterpret_cast< const vertex_pnt* >( m_model_data );
-		const vertex_pnt* prev = data + m_vertex_count * m_last_frame;
-		const vertex_pnt* next = data + m_vertex_count * m_next_frame;
-		      vertex_pnt* vtx  = reinterpret_cast<vertex_pnt*>( m_data );
-		for ( size_t i = 0; i < m_vertex_count; ++i )
-		{
-			vtx[i].position = math::mix( prev[i].position, next[i].position, m_interpolation );
-			vtx[i].normal   = math::mix( prev[i].normal,   next[i].normal,   m_interpolation );
-			vtx[i].tangent  = math::mix( prev[i].tangent,  next[i].tangent,   m_interpolation );
-		}
-	}
-	else
-	{
-		const vertex_pn* data = reinterpret_cast< const vertex_pn* >( m_model_data );
-		const vertex_pn* prev = data + m_vertex_count * m_last_frame;
-		const vertex_pn* next = data + m_vertex_count * m_next_frame;
-		      vertex_pn* vtx  = reinterpret_cast<vertex_pn*>( m_data );
-
-		for ( size_t i = 0; i < m_vertex_count; ++i )
-		{
-			vtx[i].position = math::mix( prev[i].position, next[i].position, m_interpolation );
-			vtx[i].normal   = math::mix( prev[i].normal,   next[i].normal,   m_interpolation );
-		}
-	}
-
-	m_context->update( m_pbuffer, m_data, 0, m_vertex_count * m_vsize );
-}
-
-nv::keyframed_mesh_cpu::~keyframed_mesh_cpu()
-{
-	delete[] m_data;
-}
Index: /trunk/src/gfx/mesh_creator.cc
===================================================================
--- /trunk/src/gfx/mesh_creator.cc	(revision 479)
+++ /trunk/src/gfx/mesh_creator.cc	(revision 480)
@@ -157,9 +157,4 @@
 		}
 	}
-}
-
-nv::mesh_data_creator::mesh_data_creator( data_channel_set* data ) : m_data( data )
-{
-	initialize();
 }
 
@@ -656,11 +651,2 @@
 }
 
-void nv::mesh_creator::delete_mesh( uint32 index )
-{
-	if ( index < m_pack->get_count() )
-	{
-
-		m_pack->m_meshes[index] = move( m_pack->m_meshes[m_pack->m_count - 1] );
-		m_pack->m_count--;
-	}
-}
Index: unk/src/gfx/skeletal_mesh.cc
===================================================================
--- /trunk/src/gfx/skeletal_mesh.cc	(revision 479)
+++ 	(revision )
@@ -1,73 +1,0 @@
-// Copyright (C) 2011-2015 ChaosForge Ltd
-// http://chaosforge.org/
-//
-// This file is part of Nova libraries. 
-// For conditions of distribution and use, see copying.txt file in root folder.
-
-#include "nv/gfx/skeletal_mesh.hh"
-
-#include "nv/interface/context.hh"
-#include "nv/interface/device.hh"
-#include "nv/stl/unordered_map.hh"
-
-#include "nv/core/logging.hh"
-
-void nv::skeletal_animation_entry::update_skeleton( skeleton_instance& data, uint32 a_ms_time ) const
-{
-	float  fframe = ( a_ms_time * 0.001f ) * m_fps;
-	float  nframe = nv::floor( fframe );
-	uint32 duration = get_frame_count();
-	if ( duration == 0 )
-	{
-		fframe = static_cast<float>( get_start_frame() );
-	}
-	else if ( nframe >= duration )
-	{
-		if ( is_looping() )
-			fframe = nv::fmodf( fframe, nv::f32( duration ) );
-		else
-			fframe = static_cast<float>( get_end_frame() );
-	}
-
-	if ( data.size() == 0 )
-		data.initialize( m_temp_anim->size() );
-	data.animate( m_temp_anim, m_data, fframe );
-}
-
-void nv::skeletal_animation_entry::prepare( const mesh_nodes_data* bones )
-{
-	m_data.prepare( m_temp_anim, bones ? bones : m_temp_anim );
-}
-
-nv::skeletal_mesh::skeletal_mesh( context* a_context, const data_channel_set* a_mesh, const mesh_nodes_data* a_bone_data )
-	: m_skeleton( a_bone_data ? a_bone_data->size() : 0 ), m_context( a_context ), m_bone_data( a_bone_data ), m_index_count( 0 ), m_parent_id(-1)
-{
-	if ( a_mesh )
-	{
-		m_va = a_context->create_vertex_array( a_mesh, nv::STATIC_DRAW );
-		m_index_count = a_mesh->get_channel_size( slot::INDEX );
-		m_parent_id = a_mesh->get_parent_id();
-	}
-}
-
-void nv::skeletal_mesh::update_animation( animation_entry& a_anim, uint32 a_anim_time )
-{
-	skeletal_animation_entry& anim = static_cast<skeletal_animation_entry&>( a_anim );
-	anim.prepare( m_bone_data );
-	anim.update_skeleton( m_skeleton, a_anim_time );
-}
-
-void nv::skeletal_mesh::update_program( program a_program )
-{
-	m_context->get_device()->set_opt_uniform_array( a_program, "nv_m_bones", m_skeleton.transforms(), m_skeleton.size() );
-}
-
-nv::transform nv::skeletal_mesh::get_node_transform( uint32 node_id ) const
-{
-	return transform( get_node_matrix( node_id ) );
-}
-
-nv::mat4 nv::skeletal_mesh::get_node_matrix( uint32 node_id ) const
-{
-	return m_skeleton.transforms()[ node_id ];
-}
Index: /trunk/src/gfx/skeleton_instance.cc
===================================================================
--- /trunk/src/gfx/skeleton_instance.cc	(revision 479)
+++ /trunk/src/gfx/skeleton_instance.cc	(revision 480)
@@ -6,4 +6,6 @@
 
 #include "nv/gfx/skeleton_instance.hh"
+
+#include "nv/core/profiler.hh"
 
 void nv::skeleton_binding::prepare( const mesh_nodes_data* node_data, const mesh_nodes_data* bone_data )
@@ -62,28 +64,28 @@
 			for ( uint32 n = 0; n < node_data->size(); ++n )
 				if ( ( *node_data )[n]->get_parent_id() == -1 )
-					animate_rec( node_data, binding, frame, n, mat4() );
+					animate_rec( node_data, binding, frame, n, transform() );
 		}
 	}
 }
 
-void nv::skeleton_instance::animate_rec( const mesh_nodes_data* node_data, const skeleton_binding& binding, float frame, uint32 id, const mat4& parent ) 
+void nv::skeleton_instance::animate_rec( const mesh_nodes_data* node_data, const skeleton_binding& binding, float frame, uint32 id, const transform& parent )
 {
 	// TODO: fix transforms, which are now embedded,
 	//       see note in assimp_loader.cc:load_node
 	const data_channel_set* node = ( *node_data )[id];
-	mat4 node_mat( node->get_transform() );
+	transform node_mat( node->get_transform() );
 
 	if ( node->size() > 0 )
 	{
 		raw_channel_interpolator interpolator( node, binding.m_key );
-		node_mat = interpolator.get< mat4 >( frame );
+		node_mat = interpolator.get< transform >( frame );
 	}
 
-	mat4 global_mat = parent * node_mat;
+	transform global_mat = parent * node_mat;
 
 	sint16 bone_id = binding.m_indices[id];
 	if ( bone_id >= 0 )
 	{
-		m_transform[bone_id] = global_mat * binding.m_offsets[bone_id];
+		m_transform[bone_id] = global_mat.extract() * binding.m_offsets[bone_id];
 	}
 
