Index: /trunk/nv/formats/assimp_loader.hh
===================================================================
--- /trunk/nv/formats/assimp_loader.hh	(revision 279)
+++ /trunk/nv/formats/assimp_loader.hh	(revision 280)
@@ -9,4 +9,5 @@
 
 #include <nv/common.hh>
+#include <nv/io/string_table.hh>
 #include <nv/interface/mesh_loader.hh>
 #include <nv/interface/mesh_data.hh>
@@ -76,4 +77,5 @@
 		key_animation_data* create_direct_keys( const void* vnode );
 
+		string_table_creator m_strings;
 		string m_ext;
 		mat4   m_rotate_transform;
Index: /trunk/nv/gfx/keyframed_mesh.hh
===================================================================
--- /trunk/nv/gfx/keyframed_mesh.hh	(revision 279)
+++ /trunk/nv/gfx/keyframed_mesh.hh	(revision 280)
@@ -53,4 +53,8 @@
 			vec3 normal;
 		};
+		struct vertex_t
+		{
+			vec2 texcoord;
+		};
 
 		mesh_data* m_mesh_data;
Index: /trunk/nv/interface/device.hh
===================================================================
--- /trunk/nv/interface/device.hh	(revision 279)
+++ /trunk/nv/interface/device.hh	(revision 280)
@@ -100,15 +100,18 @@
 		{
 			vertex_array*  va = create_vertex_array();
-			for ( uint32 ch = 0; ch < data->get_channel_data().size(); ++ch )
+			const std::vector< mesh_raw_channel* >& channels = data->get_raw_channels();
+			for ( uint32 ch = 0; ch < channels.size(); ++ch )
 			{
-				const mesh_raw_channel* channel = data->get_channel_data()[ch];
-				vertex_buffer* vb = create_vertex_buffer( hint, channel->size, channel->data );
-				va->add_vertex_buffers( vb, channel );
-			}
-			if ( data->get_index_channel() != nullptr )
-			{
-				const mesh_raw_index_channel* index = data->get_index_channel();
-				index_buffer* ib = create_index_buffer( hint, index->size, index->data );
-				va->set_index_buffer( ib, index->etype, true );
+				const mesh_raw_channel* channel = channels[ch];
+				if ( channel->is_index() )
+				{
+					index_buffer* ib = create_index_buffer( hint, channel->size(), channel->data );
+					va->set_index_buffer( ib, channel->desc.slots[0].etype, true );
+				}
+				else
+				{
+					vertex_buffer* vb = create_vertex_buffer( hint, channel->size(), channel->data );
+					va->add_vertex_buffers( vb, channel );
+				}
 			}
 			return va;
Index: /trunk/nv/interface/mesh_data.hh
===================================================================
--- /trunk/nv/interface/mesh_data.hh	(revision 279)
+++ /trunk/nv/interface/mesh_data.hh	(revision 280)
@@ -24,12 +24,15 @@
 		vertex_descriptor desc;
 		uint8*            data;
-		uint32            size;
 		uint32            count;
 
-		mesh_raw_channel() : data( nullptr ), size( 0 ) {}
+		mesh_raw_channel() : data( nullptr ), count( 0 ) {}
 		~mesh_raw_channel() 
 		{
 			if ( data != nullptr ) delete[] data;
 		}
+
+		bool is_index() const { return count > 0 && desc.slots[0].vslot == INDEX; }
+
+		uint32 size() const { return count * desc.size; }
 
 		template < typename VTX >
@@ -39,6 +42,5 @@
 			result->desc.initialize<VTX>();
 			result->count = count;
-			result->size  = count * sizeof( VTX );
-			result->data  = (count > 0 ? ( new uint8[ result->size ] ) : nullptr );
+			result->data  = (count > 0 ? ( new uint8[ result->size() ] ) : nullptr );
 			return result;
 		}
@@ -48,43 +50,28 @@
 			result->desc  = vtxdesc;
 			result->count = count;
-			result->size  = count * sizeof( vtxdesc.size );
-			result->data  = (count > 0 ? ( new uint8[ result->size ] ) : nullptr );
+			result->data  = (count > 0 ? ( new uint8[ result->size() ] ) : nullptr );
 			return result;
-		}
-	};
-
-	struct mesh_raw_index_channel
-	{
-		datatype          etype;
-		uint8*            data;
-		uint32            size;
-		uint32            count;
-
-		mesh_raw_index_channel() : etype( NONE ), data( nullptr ), size( 0 ) {}
-		~mesh_raw_index_channel() 
-		{
-			if ( data != nullptr ) delete[] data;
 		}
 
 		template < typename ITYPE >
-		static mesh_raw_index_channel* create( uint32 count = 0 )
+		static mesh_raw_channel* create_index( uint32 count = 0 )
 		{
-			mesh_raw_index_channel* result = new mesh_raw_index_channel();
-			result->etype = type_to_enum< ITYPE >::type;
+			mesh_raw_channel* result = new mesh_raw_channel();
+			result->desc.initialize_index<ITYPE>();
 			result->count = count;
-			result->size  = count * sizeof( ITYPE );
-			result->data  = (count > 0 ? ( new uint8[ result->size ] ) : nullptr );
+			result->data  = (count > 0 ? ( new uint8[ result->size() ] ) : nullptr );
 			return result;
 		}
 
-		static mesh_raw_index_channel* create( datatype etype, uint32 count = 0 )
+		// TODO: remove this
+		static mesh_raw_channel* create_index( datatype etype, uint32 count = 0 )
 		{
-			mesh_raw_index_channel* result = new mesh_raw_index_channel();
-			result->etype = etype;
+			mesh_raw_channel* result = new mesh_raw_channel();
+			result->desc.initialize_index( etype );
 			result->count = count;
-			result->size  = count * get_datatype_info( etype ).size;
-			result->data  = (count > 0 ? ( new uint8[ result->size ] ) : nullptr );
+			result->data  = (count > 0 ? ( new uint8[ result->size() ] ) : nullptr );
 			return result;
 		}
+
 	};
 
@@ -93,13 +80,17 @@
 	public:
 		mesh_data() : m_index_channel( nullptr ) {}
-		void add_channel( mesh_raw_channel* channel ) { m_channels.push_back( channel ); }
-		void set_index_channel( mesh_raw_index_channel* channel )
+		void add_channel( mesh_raw_channel* channel ) 
 		{
-			if ( m_index_channel ) delete m_index_channel;
-			m_index_channel = channel;
+			NV_ASSERT( channel, "nullptr passed to add_channel!" );
+			m_channels.push_back( channel );
+			if ( channel->is_index() )
+			{
+				NV_ASSERT( !m_index_channel, "second index channel!" );
+				m_index_channel = channel;
+			}
 		}
 
-		const std::vector< mesh_raw_channel* >& get_channel_data() const { return m_channels; }
-		const mesh_raw_index_channel*           get_index_channel() const { return m_index_channel; }
+		const std::vector< mesh_raw_channel* >& get_raw_channels() const { return m_channels; }
+		const mesh_raw_channel*           get_index_channel() const { return m_index_channel; }
 
 		size_t get_count() const 
@@ -116,12 +107,41 @@
 		}
 
+		template < typename VTX >
+		const mesh_raw_channel* get_channel() const
+		{
+			vertex_descriptor compare;
+			compare.initialize<VTX>();
+			for ( auto ch : m_channels )
+			{
+				if ( ch->desc == compare )
+				{
+					return ch;
+				}
+			}
+			return nullptr;
+		}
+
+		template < typename VTX >
+		const VTX* get_channel_data() const
+		{
+			vertex_descriptor compare;
+			compare.initialize<VTX>();
+			for ( auto ch : m_channels )
+			{
+				if ( ch->desc == compare )
+				{
+					return (VTX*)ch->data;
+				}
+			}
+			return nullptr;
+		}
+
 		virtual ~mesh_data()
 		{
 			for ( auto channel : m_channels ) delete channel;
-			if ( m_index_channel ) delete m_index_channel;
 		}
 	private:
 		std::vector< mesh_raw_channel* > m_channels;
-		mesh_raw_index_channel*          m_index_channel;
+		mesh_raw_channel*                m_index_channel;
 	};
 
Index: /trunk/nv/interface/vertex.hh
===================================================================
--- /trunk/nv/interface/vertex.hh	(revision 279)
+++ /trunk/nv/interface/vertex.hh	(revision 280)
@@ -26,5 +26,6 @@
 		COLOR          = 6,
 
-		SLOT_MAX       = 6,
+		INDEX          = 7,
+		SLOT_MAX       = 7,
 		SLOT_MAX_STORE = 8,
 	};
@@ -250,4 +251,23 @@
 		uint32                 size;
 
+		template < typename IDX >
+		void initialize_index()
+		{
+			count = 1;
+			size  = sizeof( IDX );
+			slots[0].etype  = type_to_enum< IDX >::type;
+			slots[0].vslot  = slot::INDEX;
+			slots[0].offset = 0;
+		}
+
+		void initialize_index( datatype itype )
+		{
+			count = 1;
+			size  = get_datatype_info( itype ).size;
+			slots[0].etype  = itype;
+			slots[0].vslot  = slot::INDEX;
+			slots[0].offset = 0;
+		}
+
 		template < typename VTX >
 		void initialize()
@@ -263,4 +283,18 @@
 			size = sizeof( VTX );
 		}
+
+		bool operator==( const vertex_descriptor& rhs )
+		{
+			if ( size  != rhs.size )  return false;
+			if ( count != rhs.count ) return false;
+			for ( uint32 i = 0; i < count; ++i )
+			{
+				if ( slots[i].etype  != rhs.slots[i].etype )  return false;
+				if ( slots[i].offset != rhs.slots[i].offset ) return false;
+				if ( slots[i].vslot  != rhs.slots[i].vslot )  return false;
+			}
+			return true;
+		}
+
 	private:
 		template < typename VTX, slot SLOT >
Index: /trunk/nv/io/string_table.hh
===================================================================
--- /trunk/nv/io/string_table.hh	(revision 279)
+++ /trunk/nv/io/string_table.hh	(revision 280)
@@ -20,10 +20,12 @@
 namespace nv
 {
-
 	class string_table : noncopyable
 	{
 	public:
-		string_table( char* data, uint32 size, uint32* indices, uint32 count )
-			: m_count( count ), m_size( size ), m_data( data ), m_indices( indices )
+		typedef uint16 index;
+		typedef uint32 offset;
+
+		string_table( char* data, uint32 size, offset* offsets, index count )
+			: m_count( count ), m_size( size ), m_data( data ), m_offsets( offsets )
 		{
 
@@ -34,14 +36,14 @@
 		}
 
-		const char* get( uint16 index )
+		const char* get( index i ) const
 		{
-			return index < m_count ? m_data + m_indices[index] : nullptr;
+			return i < m_count ? m_data + m_offsets[i] : nullptr;
 		}
 
-		void dump( nv::stream* out )
+		void dump( nv::stream* out ) const
 		{
 			out->write( &m_count,  sizeof( m_count ), 1 );
 			out->write( &m_size,   sizeof( m_size ), 1 );
-			out->write( m_indices, sizeof( uint32 ), m_count );
+			out->write( m_offsets, sizeof( offset ), m_count );
 			out->write( m_data,    sizeof( char ), m_size );
 		}
@@ -50,5 +52,5 @@
 		{
 			delete[] m_data;
-			delete[] m_indices;
+			delete[] m_offsets;
 		}
 	private:
@@ -57,14 +59,14 @@
 			in->read( &m_count, sizeof( m_count ), 1 );
 			in->read( &m_size, sizeof( m_size ), 1 );
-			m_indices = new uint32[ m_count ];
+			m_offsets = new offset[ m_count ];
 			m_data    = new char[ m_count ];
-			in->read( m_indices, sizeof( uint32 ), m_count );
+			in->read( m_offsets, sizeof( offset ), m_count );
 			in->read( m_data,    sizeof( char ), m_size );
 		}
 
-		uint32  m_count;
+		char*   m_data;
 		uint32  m_size;
-		uint32* m_indices;
-		char*   m_data;
+		offset* m_offsets;
+		index   m_count;
 	};
 
@@ -72,49 +74,17 @@
 	{
 	public:
-		string_table_creator() {}
+		typedef string_table::index  index;
+		typedef string_table::offset offset;
 
-		uint32 insert( const char* s )
-		{
-			return insert( std::string(s) );
-		}
-
-		uint32 insert( const std::string& s )
-		{
-			auto i = m_map.find( s );
-			if ( i != m_map.end() )
-			{
-				return i->second;
-			}
-			const char* cs = s.c_str();
-			uint32 cs_size = s.size() + 1;
-			uint32 result = m_indexes.size();
-			uint32 index  = m_data.size();
-			m_indexes.push_back( index );
-			std::copy( cs, cs + cs_size, std::back_inserter( m_data ) );
-			m_map[ s ] = result;
-			return result;
-		}
-
-		string_table* create_table() const
-		{
-			uint32* indexes = new uint32[m_indexes.size()];
-			char*   data    = new char  [m_data.size()];
-			std::copy( m_indexes.begin(), m_indexes.end(), indexes );
-			std::copy( m_data.begin(),    m_data.end(),    data );
-			return new string_table( data, m_data.size(), indexes, m_indexes.size() );
-		}
-
-		void dump( nv::stream* out ) const
-		{
-			uint32 count = m_indexes.size();
-			uint32 size  = m_data.size();
-			out->write( &count,  sizeof( count ), 1 );
-			out->write( &size,   sizeof( size ), 1 );
-			out->write( m_indexes.data(), sizeof( uint16 ), count );
-			out->write( m_data.data(),    sizeof( char ), size );
-		}
+		string_table_creator();
+		index insert( const std::string& s );
+		string_table* create_table() const;
+		void dump( nv::stream* out ) const;
+		const char* get( index i ) const;
+		index get( const std::string& s ) const;
+		void clear();
 	private:
-		std::unordered_map< std::string, uint32 > m_map;
-		std::vector< uint32 > m_indexes;
+		std::unordered_map< std::string, index > m_map;
+		std::vector< offset > m_offsets;
 		std::vector< char >   m_data;
 	};
Index: /trunk/src/formats/assimp_loader.cc
===================================================================
--- /trunk/src/formats/assimp_loader.cc	(revision 279)
+++ /trunk/src/formats/assimp_loader.cc	(revision 280)
@@ -143,6 +143,6 @@
 	}
 
-	mesh_raw_index_channel* ichannel = mesh_raw_index_channel::create( USHORT, mesh->mNumFaces * 3 );
-	result->set_index_channel( ichannel );
+	mesh_raw_channel* ichannel = mesh_raw_channel::create_index( USHORT, mesh->mNumFaces * 3 );
+	result->add_channel( ichannel );
 	uint16* indices = (uint16*)ichannel->data;
 	for (unsigned int i=0; i<mesh->mNumFaces; i++)
@@ -294,5 +294,6 @@
 		{
 			mesh_data* mesh = model->meshes[m];
-			mesh_raw_channel* channel = mesh->get_channel_data()[0];
+			mesh_raw_channel* channel = mesh->get_raw_channels()[0];
+			NV_ASSERT( !channel->is_index(), "index channel in release_merged!" );
 			assimp_skinned_vtx* va = (assimp_skinned_vtx*)channel->data;
 			for ( unsigned v = 0; v < channel->count; ++v )
Index: /trunk/src/formats/md2_loader.cc
===================================================================
--- /trunk/src/formats/md2_loader.cc	(revision 279)
+++ /trunk/src/formats/md2_loader.cc	(revision 280)
@@ -359,5 +359,5 @@
 	}
 
-	mesh_raw_index_channel* ic = mesh_raw_index_channel::create< uint16 >( m_new_indexes.size() );
+	mesh_raw_channel* ic = mesh_raw_channel::create_index< uint16 >( m_new_indexes.size() );
 	if ( m_new_indexes.size() > 0 )
 	{
@@ -369,5 +369,5 @@
 	result->add_channel( mc_pn );
 	result->add_channel( mc_t );
-	result->set_index_channel( ic );
+	result->add_channel( ic );
 	return result;
 }
Index: /trunk/src/formats/md3_loader.cc
===================================================================
--- /trunk/src/formats/md3_loader.cc	(revision 279)
+++ /trunk/src/formats/md3_loader.cc	(revision 280)
@@ -377,5 +377,5 @@
 	index = 0;
 	sint32 index_base = 0;
-	mesh_raw_index_channel* ic = mesh_raw_index_channel::create< uint16 >( index_count );
+	mesh_raw_channel* ic = mesh_raw_channel::create_index< uint16 >( index_count );
 	uint16* icp = (uint16*)ic->data;
 	for ( sint32 i = 0; i < num_surfaces; ++i )
@@ -396,5 +396,5 @@
 	result->add_channel( mc_pn );
 	result->add_channel( mc_t );
-	result->set_index_channel( ic );
+	result->add_channel( ic );
 	return result;
 }
Index: /trunk/src/formats/md5_loader.cc
===================================================================
--- /trunk/src/formats/md5_loader.cc	(revision 279)
+++ /trunk/src/formats/md5_loader.cc	(revision 280)
@@ -145,9 +145,9 @@
 					sstream >> num_tris;
 
-					mesh_raw_index_channel* ch_i = mesh_raw_index_channel::create<uint32>( num_tris * 3 );
+					mesh_raw_channel* ch_i = mesh_raw_channel::create_index<uint32>( num_tris * 3 );
 					uint32* vtx_i                = (uint32*)ch_i->data;
 					mesh->m_idata                = vtx_i;
 					uint32 idx = 0;
-					mesh->set_index_channel( ch_i );
+					mesh->add_channel( ch_i );
 
 					next_line( sstream );
Index: /trunk/src/formats/obj_loader.cc
===================================================================
--- /trunk/src/formats/obj_loader.cc	(revision 279)
+++ /trunk/src/formats/obj_loader.cc	(revision 280)
@@ -334,5 +334,4 @@
 		channel->desc  = m_descriptor;
 		channel->count = reader->size * 3;
-		channel->size  = reader->raw_size();
 
 		mesh_data* mesh = new mesh_data();
Index: /trunk/src/gfx/keyframed_mesh.cc
===================================================================
--- /trunk/src/gfx/keyframed_mesh.cc	(revision 279)
+++ /trunk/src/gfx/keyframed_mesh.cc	(revision 280)
@@ -32,6 +32,6 @@
 
 	m_index_count  = m_mesh_data->get_index_channel()->count;
-	m_vertex_count = m_mesh_data->get_channel_data()[1]->count;
-	m_frame_count  = m_mesh_data->get_channel_data()[0]->count / m_vertex_count;
+	m_vertex_count = m_mesh_data->get_channel<vertex_t>()->count;
+	m_frame_count  = m_mesh_data->get_channel<vertex_pn>()->count / m_vertex_count;
 }
 
@@ -167,12 +167,12 @@
 	: keyframed_mesh( a_device, a_data, a_tag_map )
 {
-	m_vb = a_device->create_vertex_buffer( nv::STATIC_DRAW, m_vertex_count * sizeof( vertex_pn ), (void*)m_mesh_data->get_channel_data()[0]->data );
-	m_va->add_vertex_buffers( m_vb, m_mesh_data->get_channel_data()[0] );
+	m_vb = a_device->create_vertex_buffer( nv::STATIC_DRAW, m_vertex_count * sizeof( vertex_pn ), (void*)m_mesh_data->get_channel<vertex_pn>()->data );
+	m_va->add_vertex_buffers( m_vb, m_mesh_data->get_channel<vertex_pn>() );
 
-	nv::vertex_buffer* vb = a_device->create_vertex_buffer( nv::STATIC_DRAW, m_vertex_count * sizeof( nv::vec2 ), (void*)m_mesh_data->get_channel_data()[1]->data );
-	m_va->add_vertex_buffers( vb, m_mesh_data->get_channel_data()[1] );
+	nv::vertex_buffer* vb = a_device->create_vertex_buffer( nv::STATIC_DRAW, m_vertex_count * sizeof( nv::vec2 ), (void*)m_mesh_data->get_channel<vertex_t>()->data );
+	m_va->add_vertex_buffers( vb, m_mesh_data->get_channel<vertex_t>() );
 
-	nv::index_buffer* ib = a_device->create_index_buffer( nv::STATIC_DRAW, m_mesh_data->get_index_channel()->size, (void*)m_mesh_data->get_index_channel()->data );
-	m_va->set_index_buffer( ib, m_mesh_data->get_index_channel()->etype, true );
+	nv::index_buffer* ib = a_device->create_index_buffer( nv::STATIC_DRAW, m_mesh_data->get_index_channel()->size(), (void*)m_mesh_data->get_index_channel()->data );
+	m_va->set_index_buffer( ib, m_mesh_data->get_index_channel()->desc.slots[0].etype, true );
 
 	m_vertex.resize( m_vertex_count );
@@ -183,5 +183,5 @@
 	keyframed_mesh::update( ms );
 
-	const vertex_pn* data = (const vertex_pn*)(m_mesh_data->get_channel_data()[0]->data);
+	const vertex_pn* data = m_mesh_data->get_channel_data<vertex_pn>();
 	const vertex_pn* prev = data + m_vertex_count * m_last_frame;
 	const vertex_pn* next = data + m_vertex_count * m_next_frame;
Index: /trunk/src/io/string_table.cc
===================================================================
--- /trunk/src/io/string_table.cc	(revision 280)
+++ /trunk/src/io/string_table.cc	(revision 280)
@@ -0,0 +1,71 @@
+// Copyright (C) 2014 ChaosForge Ltd
+// http://chaosforge.org/
+//
+// This file is part of NV Libraries.
+// For conditions of distribution and use, see copyright notice in nv.hh
+
+#include "nv/io/string_table.hh"
+
+nv::string_table_creator::string_table_creator()
+{
+	insert(""); // 0 always is empty string
+}
+
+nv::string_table_creator::index nv::string_table_creator::insert( const std::string& s )
+{
+	auto i = m_map.find( s );
+	if ( i != m_map.end() )
+	{
+		return i->second;
+	}
+	const char* cs = s.c_str();
+	uint32 cs_size = s.size() + 1;
+	NV_ASSERT( m_offsets.size() < index(-1), "Too many strings!" );
+	index  result  = (index)m_offsets.size();
+	m_offsets.push_back( m_data.size() );
+	std::copy( cs, cs + cs_size, std::back_inserter( m_data ) );
+	m_map[ s ] = result;
+	return result;
+}
+
+nv::string_table* nv::string_table_creator::create_table() const
+{
+	offset* offsets = new offset[m_offsets.size()];
+	char*   data    = new char [m_data.size()];
+	std::copy( m_offsets.begin(), m_offsets.end(), offsets );
+	std::copy( m_data.begin(),    m_data.end(),    data );
+	return new string_table( data, m_data.size(), offsets, (index)m_offsets.size() );
+}
+
+void nv::string_table_creator::dump( nv::stream* out ) const
+{
+	index  count = (index)m_offsets.size();
+	uint32 size  = m_data.size();
+	out->write( &count,  sizeof( count ), 1 );
+	out->write( &size,   sizeof( size ), 1 );
+	out->write( m_offsets.data(), sizeof( offset ), count );
+	out->write( m_data.data(),    sizeof( char ),  size );
+}
+
+const char* nv::string_table_creator::get( index i ) const
+{
+	return i < m_offsets.size() ? m_data.data() + m_offsets[i] : nullptr;
+}
+
+nv::string_table_creator::index nv::string_table_creator::get( const std::string& s ) const
+{
+	auto i = m_map.find( s );
+	if ( i != m_map.end() )
+	{
+		return i->second;
+	}
+	return 0;
+}
+
+void nv::string_table_creator::clear()
+{
+	m_map.clear();
+	m_offsets.clear();
+	m_data.clear();
+}
+
