Index: /trunk/nv/detail/io_event_list.inc
===================================================================
--- /trunk/nv/detail/io_event_list.inc	(revision 303)
+++ /trunk/nv/detail/io_event_list.inc	(revision 304)
@@ -2,4 +2,5 @@
 NV_IO_EVENT( EV_MOUSE_BUTTON )
 NV_IO_EVENT( EV_MOUSE_MOVE )
+NV_IO_EVENT( EV_MOUSE_WHEEL )
 NV_IO_EVENT( EV_JOY_BUTTON )
 NV_IO_EVENT( EV_JOY_AXIS )
Index: /trunk/nv/detail/mouse_list.inc
===================================================================
--- /trunk/nv/detail/mouse_list.inc	(revision 303)
+++ /trunk/nv/detail/mouse_list.inc	(revision 304)
@@ -3,4 +3,2 @@
 NV_MOUSE( MOUSE_MIDDLE,     2 )
 NV_MOUSE( MOUSE_RIGHT,      3 )
-NV_MOUSE( MOUSE_WHEEL_UP,   4 )
-NV_MOUSE( MOUSE_WHEEL_DOWN, 5 )
Index: /trunk/nv/formats/md3_loader.hh
===================================================================
--- /trunk/nv/formats/md3_loader.hh	(revision 303)
+++ /trunk/nv/formats/md3_loader.hh	(revision 304)
@@ -32,5 +32,5 @@
 	{
 	public:
-		md3_loader();
+		md3_loader( bool merge_all = true );
 		virtual ~md3_loader();
 		virtual bool load( stream& source );
@@ -42,6 +42,7 @@
 		virtual mesh_nodes_data* release_mesh_nodes_data( size_t = 0 );
 	private:
-		void release_mesh_frame( mesh_data* data, sint32 frame );
+		void release_mesh_frame( mesh_data* data, sint32 frame, sint32 surface );
 		key_raw_channel* load_tags( const std::string& tag );
+		bool m_merge_all;
 		void* m_md3;
 	};
Index: /trunk/nv/gfx/keyframed_mesh.hh
===================================================================
--- /trunk/nv/gfx/keyframed_mesh.hh	(revision 303)
+++ /trunk/nv/gfx/keyframed_mesh.hh	(revision 304)
@@ -28,4 +28,5 @@
 		virtual void update_animation( animation_entry*, uint32 a_anim_time );
 		virtual void update( program a_program );
+		virtual void update( uint32 ms ) { animated_mesh::update(ms); }
 		virtual ~keyframed_mesh();
 	protected:
Index: /trunk/nv/gfx/mesh_creator.hh
===================================================================
--- /trunk/nv/gfx/mesh_creator.hh	(revision 303)
+++ /trunk/nv/gfx/mesh_creator.hh	(revision 304)
@@ -50,4 +50,13 @@
 	public:
 		mesh_creator( mesh_data_pack* pack ) : m_pack( pack ) {}
+		void delete_mesh( uint32 index )
+		{
+			if ( index < m_pack->get_count() )
+			{
+				m_pack->m_meshes[ index ].destroy();
+				m_pack->m_meshes[ m_pack->m_count-1 ].move_to( m_pack->m_meshes[ index ] );
+				m_pack->m_count--;
+			}
+		}
 		// assumes that keys are equally spaced
 		void pre_transform_keys() 
Index: /trunk/nv/interface/mesh_data.hh
===================================================================
--- /trunk/nv/interface/mesh_data.hh	(revision 303)
+++ /trunk/nv/interface/mesh_data.hh	(revision 304)
@@ -202,7 +202,14 @@
 		}
 
+		void destroy()
+		{
+			for ( auto channel : m_channels ) delete channel;
+			m_channels.clear();
+			m_index_channel = nullptr;
+		}
+
 		virtual ~mesh_data()
 		{
-			for ( auto channel : m_channels ) delete channel;
+			destroy();
 		}
 	private:
Index: /trunk/nv/io_event.hh
===================================================================
--- /trunk/nv/io_event.hh	(revision 303)
+++ /trunk/nv/io_event.hh	(revision 304)
@@ -77,4 +77,12 @@
 		/// Mouse button code
 		mouse_code code;
+	};
+
+	struct mouse_wheel_event
+	{
+		/// amount scrolled horizontally positive to the right
+		sint32 x;
+		/// amount scrolled vertically 
+		sint32 y;
 	};
 
@@ -166,4 +174,5 @@
 			mouse_button_event mbutton;
 			mouse_move_event   mmove;
+			mouse_wheel_event  mwheel;
 			joy_button_event   jbutton;
 			joy_axis_event     jaxis;
Index: /trunk/src/formats/md3_loader.cc
===================================================================
--- /trunk/src/formats/md3_loader.cc	(revision 303)
+++ /trunk/src/formats/md3_loader.cc	(revision 304)
@@ -233,6 +233,6 @@
 static bool s_normal_ready = false;
 
-md3_loader::md3_loader()
-	: m_md3( nullptr )
+md3_loader::md3_loader( bool merge_all )
+	: m_md3( nullptr ), m_merge_all( merge_all )
 {
 	if ( !s_normal_ready )
@@ -319,28 +319,77 @@
 };
 
-mesh_data* nv::md3_loader::release_mesh_data( size_t )
+mesh_data* nv::md3_loader::release_mesh_data( size_t index )
 {
 	mesh_data* data = new mesh_data;
-	release_mesh_frame( data, -1 );
+	release_mesh_frame( data, -1, index );
 	return data;
 }
 
-void nv::md3_loader::release_mesh_frame( mesh_data* data, sint32 frame )
+void nv::md3_loader::release_mesh_frame( mesh_data* data, sint32 frame, sint32 surface )
 {
 	md3_t* md3 = (md3_t*)m_md3;
-	sint32 num_surfaces = md3->header.num_surfaces;
-	sint32 num_verts    = md3->vertices_per_frame;
+	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;
+		}
 
 	mesh_raw_channel* mc_pn = mesh_raw_channel::create< vtx_md3_pn >( num_verts * frame_count );
+	mesh_raw_channel* mc_t  = mesh_raw_channel::create< vtx_md3_t >( num_verts );
+	mesh_raw_channel* ic = mesh_raw_channel::create_index< uint16 >( index_count );
 	vtx_md3_pn* vtx_pn = (vtx_md3_pn*)mc_pn->data;
-
-	uint32 index = 0;
+	vtx_md3_t*  vtx_t  = (vtx_md3_t*) mc_t->data;
+	uint16*     icp    = (uint16*)ic->data;
+
+	uint32 index  = 0;
+	uint32 iindex = 0;
+	sint32 index_base = 0;
+
+	while ( surf_count > 0 )
+	{
+		const md3_surface_t& surface = md3->surfaces[ current_surf ];
+		const uint32         vcount  = static_cast< uint32 >( surface.header.num_verts );
+		const uint32         tcount  = static_cast< uint32 >( surface.header.num_triangles );
+
+		for (uint32 j = 0; j < vcount; ++j )
+		{
+			vtx_t[index++].texcoord = md3_texcoord( surface.st[j] );
+		}
+
+		for (size_t j = 0; j < tcount; ++j )
+		{
+			const md3_triangle_t& t = surface.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 += surface.header.num_verts;
+		++current_surf;
+		--surf_count;
+	}
+
+	index = 0;
 	while ( frame_count > 0 )
 	{
-		for ( sint32 i = 0; i < num_surfaces; ++i )
-		{
-			md3_surface_t& surface = md3->surfaces[i];
+		current_surf  = ( surface == -1 ? 0 : surface );
+		surf_count    = ( surface == -1 ? md3->header.num_surfaces : 1 );
+
+		while ( surf_count > 0 )
+		{
+			md3_surface_t& surface = md3->surfaces[current_surf];
 			sint32         vcount  = surface.header.num_verts;
 			sint32         offset  = vcount * current_frame;
@@ -353,45 +402,9 @@
 				index++;
 			}
-
+			++current_surf;
+			--surf_count;
 		}
 		++current_frame;
 		--frame_count;
-	}
-
-	index = 0;
-	mesh_raw_channel* mc_t = mesh_raw_channel::create< vtx_md3_t >( num_verts );
-	vtx_md3_t* vtx_t = (vtx_md3_t*)mc_t->data;
-	for ( sint32 i = 0; i < num_surfaces; ++i )
-	{
-		const md3_surface_t& surface = md3->surfaces[i];
-		const uint32         vcount  = static_cast< uint32 >( surface.header.num_verts );
-		for (uint32 j = 0; j < vcount; ++j )
-		{
-			vtx_t[index++].texcoord = md3_texcoord( surface.st[j] );
-		}
-	}
-
-	sint32 index_count = 0;
-	for ( sint32 i = 0; i < num_surfaces; ++i )
-	{
-		index_count += md3->surfaces[i].header.num_triangles * 3;
-	}
-
-	index = 0;
-	sint32 index_base = 0;
-	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 )
-	{
-		const md3_surface_t& surface = md3->surfaces[i];
-		const size_t         tcount  = static_cast< size_t >( surface.header.num_triangles );
-		for (size_t j = 0; j < tcount; ++j )
-		{
-			const md3_triangle_t& t = surface.triangles[j];
-			icp[index++] = static_cast< uint16 >( index_base + t.indexes[0] );
-			icp[index++] = static_cast< uint16 >( index_base + t.indexes[1] );
-			icp[index++] = static_cast< uint16 >( index_base + t.indexes[2] );
-		}
-		index_base += surface.header.num_verts;
 	}
 
@@ -427,7 +440,24 @@
 mesh_data_pack* nv::md3_loader::release_mesh_data_pack()
 {
-	mesh_data* data = new mesh_data[1];
-	release_mesh_frame( &data[0], -1 );
-	return new mesh_data_pack( 1, data, release_mesh_nodes_data() );
+	md3_t* md3 = (md3_t*)m_md3;
+	uint32 count = 1;
+	mesh_data* data = nullptr;
+	if ( m_merge_all )
+	{
+		data = new mesh_data[1];
+		release_mesh_frame( &data[0], -1, -1 );
+		data[0].set_name( (char*)md3->header.name );
+	}
+	else
+	{
+		count = md3->header.num_surfaces;
+		data = new mesh_data[ count ];
+		for ( uint32 i = 0; i < count; ++i )
+		{
+			release_mesh_frame( &data[i], -1, i );
+			data[i].set_name( (char*)md3->surfaces[i].header.name );
+		}
+	}
+	return new mesh_data_pack( count, data, release_mesh_nodes_data() );
 }
 
Index: /trunk/src/gfx/keyframed_mesh.cc
===================================================================
--- /trunk/src/gfx/keyframed_mesh.cc	(revision 303)
+++ /trunk/src/gfx/keyframed_mesh.cc	(revision 304)
@@ -47,5 +47,5 @@
 transform keyframed_mesh::get_node_transform( uint32 node_id ) const
 {
-	NV_ASSERT( m_tag_map, "TAGMAP FAIL" );
+	if ( !m_tag_map ) return transform();
 	NV_ASSERT( node_id < m_tag_map->get_count(), "TAGMAP FAIL" );
 	const key_data* data = m_tag_map->get_node( node_id )->data;
Index: /trunk/src/gl/gl_window.cc
===================================================================
--- /trunk/src/gl/gl_window.cc	(revision 303)
+++ /trunk/src/gl/gl_window.cc	(revision 304)
@@ -115,6 +115,16 @@
 	case SDL_BUTTON_MIDDLE    : mevent.mbutton.button = MOUSE_MIDDLE; break;
 	case SDL_BUTTON_RIGHT     : mevent.mbutton.button = MOUSE_RIGHT; break;
-	//case SDL_BUTTON_WHEELUP   : mevent.mbutton.button = MOUSE_WHEEL_UP; break;
-	//case SDL_BUTTON_WHEELDOWN : mevent.mbutton.button = MOUSE_WHEEL_DOWN; break;
+#if NV_SDL_VERSION == NV_SDL_12
+	case SDL_BUTTON_WHEELUP   : 
+		mevent.type           = EV_MOUSE_WHEEL;
+		mevent.mwheel.x       = 0;
+		mevent.mwheel.y       = 3;
+		return true;
+	case SDL_BUTTON_WHEELDOWN : 
+		mevent.type           = EV_MOUSE_WHEEL;
+		mevent.mwheel.x       = 0;
+		mevent.mwheel.y       = -3;
+		return true;
+#endif
 	default : break;
 	}
@@ -122,4 +132,14 @@
 	return mevent.mbutton.button != MOUSE_NONE;
 }
+
+#if NV_SDL_VERSION == NV_SDL_20
+static bool sdl_mouse_wheel_to_io_event( const SDL_MouseWheelEvent& mm, io_event& mevent )
+{
+	mevent.type          = EV_MOUSE_WHEEL;
+	mevent.mwheel.x      = static_cast< sint32 >( mm.x );
+	mevent.mwheel.y      = static_cast< sint32 >( mm.y );
+	return true;
+}
+#endif
 
 static bool sdl_mouse_motion_to_io_event( const SDL_MouseMotionEvent& mm, io_event& mevent )
@@ -180,4 +200,7 @@
 	case SDL_MOUSEBUTTONDOWN : return sdl_mouse_button_to_io_event( e.button, ioevent );
 	case SDL_MOUSEBUTTONUP   : return sdl_mouse_button_to_io_event( e.button, ioevent );
+#if NV_SDL_VERSION == NV_SDL_20
+	case SDL_MOUSEWHEEL      : return sdl_mouse_wheel_to_io_event( e.wheel, ioevent );
+#endif
 /* // SDL 2.0 incompatible 
 	case SDL_ACTIVEEVENT     : 
Index: /trunk/src/io_event.cc
===================================================================
--- /trunk/src/io_event.cc	(revision 303)
+++ /trunk/src/io_event.cc	(revision 304)
@@ -96,4 +96,10 @@
 	db->create_type<mouse_move_event>("mouse_move_event").fields( mouse_move_fields );
 
+	type_field mouse_wheel_fields[] = {
+		type_field( "x",       &mouse_wheel_event::x ),
+		type_field( "y",       &mouse_wheel_event::y ),
+	};
+	db->create_type<mouse_wheel_event>("mouse_wheel_event").fields( mouse_wheel_fields );
+
 	type_field joy_button_fields[] = {
 		type_field( "id",      &joy_button_event::id ),
Index: /trunk/tests/md3_test/md3_test.cc
===================================================================
--- /trunk/tests/md3_test/md3_test.cc	(revision 303)
+++ /trunk/tests/md3_test/md3_test.cc	(revision 304)
@@ -7,6 +7,4 @@
 #include <nv/interface/context.hh>
 #include <nv/interface/window.hh>
-#include <nv/interface/program.hh>
-#include <nv/interface/texture2d.hh>
 #include <nv/interface/mesh_loader.hh>
 #include <nv/io/c_file_system.hh>
@@ -22,10 +20,10 @@
 #include <glm/gtx/matrix_interpolation.hpp>
 
-bool GPU_ANIMATION = false;
+bool GPU_ANIMATION = true;
 
 class mesh_part
 {
 public:
-	mesh_part( const std::string& path, nv::program* program, nv::window* window )
+	mesh_part( const std::string& path, nv::window* window )
 	{
 		NV_PROFILE("mesh_construct");
@@ -42,32 +40,34 @@
 		{
 			NV_PROFILE("create_mesh_data");
-			m_mesh_data = loader->release_mesh_data();
-			m_tag_map   = loader->create_tag_map();
+			m_mesh_data = loader->release_mesh_data_pack();
 		}
 		delete loader;
-
+		m_entry = nullptr;
 		{
 			NV_PROFILE("create_mesh");
 			if ( GPU_ANIMATION )
-				m_mesh      = new nv::keyframed_mesh_gpu( window->get_context(), m_mesh_data, m_tag_map, program );
+				m_mesh      = new nv::keyframed_mesh_gpu( window->get_context(), m_mesh_data->get_mesh(0), m_mesh_data->get_nodes() );
 			else
-				m_mesh      = new nv::keyframed_mesh_cpu( window->get_context(), m_mesh_data, m_tag_map );
-		}
-
-	}
-
-	nv::mat4 get_transform( const std::string& tag )
-	{
-		return m_mesh->get_tag( tag ).extract();
+				m_mesh      = new nv::keyframed_mesh_cpu( window->get_context(), m_mesh_data->get_mesh(0), m_mesh_data->get_nodes() );
+		}
+
+	}
+
+	nv::mat4 get_transform( nv::uint32 id )
+	{
+		return m_mesh->get_node_transform( id ).extract();
 	}
 
 	void setup_animation( nv::uint32 start, nv::uint32 stop, nv::uint32 fps, bool loop )
 	{
-		m_mesh->setup_animation( start, stop, fps, loop );
-	}
-
-	void update( nv::uint32 ms, nv::program* program )
+		delete m_entry;
+		m_entry = new nv::animation_entry("test", loop, fps, (float)start, float(stop-1) );
+		m_mesh->run_animation( m_entry );
+	}
+
+	void update( nv::uint32 ms, nv::program program )
 	{
 		m_mesh->update( ms );
+		m_mesh->update_animation( m_entry, ms );
 		m_mesh->update( program );
 	}
@@ -82,5 +82,4 @@
 	~mesh_part()
 	{
-		delete m_tag_map;
 		delete m_mesh_data;
 		delete m_mesh;
@@ -88,7 +87,7 @@
 
 private:
-	nv::tag_map*        m_tag_map;
-	nv::mesh_data*      m_mesh_data;
-	nv::keyframed_mesh* m_mesh;
+	nv::mesh_data_pack*  m_mesh_data;
+	nv::keyframed_mesh*  m_mesh;
+	nv::animation_entry* m_entry;
 };
 
@@ -102,7 +101,8 @@
 protected:
 	nv::device*       m_device;
+	nv::context*      m_context;
 	nv::window*       m_window;
-	nv::texture2d*    m_diffuse;
-	nv::texture2d*    m_diffuse_weapon;
+	nv::texture       m_diffuse;
+	nv::texture       m_diffuse_weapon;
 
 	nv::clear_state   m_clear_state;
@@ -114,5 +114,5 @@
 	mesh_part* m_head;
 	mesh_part* m_weapon;
-	nv::program* m_program;
+	nv::program m_program;
 };
 
@@ -120,13 +120,14 @@
 {
 	NV_PROFILE( "app_construct" );
-	m_device = new nv::gl_device();
-	m_window = m_device->create_window( 800, 600, false );
+	m_device  = new nv::gl_device();
+	m_window  = m_device->create_window( 800, 600, false );
+	m_context = m_window->get_context();
 
 	nv::sampler sampler( nv::sampler::LINEAR, nv::sampler::REPEAT );
 	nv::image_data* data = m_device->create_image_data( "data/doom.png" );
-	m_diffuse  = m_device->create_texture2d( data->get_size(), nv::RGBA, nv::UBYTE, sampler, (void*)data->get_data() );
+	m_diffuse  = m_device->create_texture( data->get_size(), nv::image_format( nv::RGBA, nv::UBYTE ), sampler, (void*)data->get_data() );
 	delete data;
 	data = m_device->create_image_data( "data/rocketl.png" );
-	m_diffuse_weapon = m_device->create_texture2d( data->get_size(), nv::RGBA, nv::UBYTE, sampler, (void*)data->get_data() );
+	m_diffuse_weapon = m_device->create_texture( data->get_size(), nv::image_format( nv::RGBA, nv::UBYTE ), sampler, (void*)data->get_data() );
 	delete data;
 
@@ -148,8 +149,8 @@
 		nv::slurp( "obj.frag" ) 
 	);
-	m_torso   = new mesh_part( "data/upper.md3", m_program, m_window );
-	m_legs    = new mesh_part( "data/lower.md3", m_program, m_window );
-	m_head    = new mesh_part( "data/head.md3", m_program, m_window );
-	m_weapon  = new mesh_part( "data/rocketl.md3", m_program, m_window );
+	m_torso   = new mesh_part( "data/upper.md3", m_window );
+	m_legs    = new mesh_part( "data/lower.md3", m_window );
+	m_head    = new mesh_part( "data/head.md3", m_window );
+	m_weapon  = new mesh_part( "data/rocketl.md3", m_window );
 	return true;
 }
@@ -176,9 +177,9 @@
 		ticks      = m_device->get_ticks();
 		nv::uint32 elapsed = ticks - last_ticks;
-		m_torso->update( elapsed, m_program );
-		m_legs->update( elapsed, m_program );
+		m_torso->update( ticks, m_program );
+		m_legs->update( ticks, m_program );
 		{
 			NV_PROFILE( "clear" );
-			m_window->get_context()->clear( m_clear_state );
+			m_context->clear( m_clear_state );
 		}
 
@@ -194,8 +195,8 @@
 			m_scene_state.get_camera().set_perspective(60.0f, 1.0f*800.0f/600.0f, 0.1f, 1000.0f);
 
-			m_diffuse->bind( 0 );
-			m_program->set_uniform( "light_position", glm::vec3(120.0, 120.0, 0) );
-			m_program->set_uniform( "light_diffuse",  glm::vec4(1.0,1.0,1.0,1.0) );
-			m_program->set_uniform( "light_specular", glm::vec4(1.0,1.0,1.0,1.0) );
+			m_context->bind( m_diffuse, nv::TEX_DIFFUSE );
+			m_device->set_uniform( m_program, "light_position", glm::vec3(120.0, 120.0, 0) );
+			m_device->set_uniform( m_program, "light_diffuse",  glm::vec4(1.0,1.0,1.0,1.0) );
+			m_device->set_uniform( m_program, "light_specular", glm::vec4(1.0,1.0,1.0,1.0) );
 		}
 
@@ -208,16 +209,16 @@
 
 			//model = m_legs->get_transform( "tag_torso", last_legs_frame, legs_frame, legs_interpolate );
-			model = m_legs->get_transform( "tag_torso" );
+			model = m_legs->get_transform( 0 );
 			m_scene_state.set_model( model );
 			m_window->get_context()->draw( m_render_state, m_scene_state, m_program, m_torso->get_mesh() );
 
-			glm::mat4 head = model * m_torso->get_transform( "tag_head" ); //, last_torso_frame, torso_frame, torso_interpolate );
+			glm::mat4 head = model * m_torso->get_transform( 0 ); //, last_torso_frame, torso_frame, torso_interpolate );
 			m_scene_state.set_model( head );
 			m_window->get_context()->draw( m_render_state, m_scene_state, m_program, m_head->get_mesh() );
 
-			glm::mat4 weapon = model * m_torso->get_transform( "tag_weapon" ); //, last_torso_frame, torso_frame, torso_interpolate );
+			glm::mat4 weapon = model * m_torso->get_transform( 2 ); //, last_torso_frame, torso_frame, torso_interpolate );
 			m_scene_state.set_model( weapon );
-			m_diffuse_weapon->bind( 0 );
-			m_window->get_context()->draw( m_render_state, m_scene_state, m_program, m_weapon->get_mesh() );
+			m_context->bind( m_diffuse_weapon, nv::TEX_DIFFUSE );
+			m_context->draw( m_render_state, m_scene_state, m_program, m_weapon->get_mesh() );
 
 		}
@@ -273,11 +274,11 @@
 application::~application()
 {
-	delete m_program;
+	m_device->release( m_program );
 	delete m_torso;
 	delete m_legs;
 	delete m_head;
 	delete m_weapon;
-	delete m_diffuse;
-	delete m_diffuse_weapon;
+	m_device->release( m_diffuse );
+	m_device->release( m_diffuse_weapon );
 	delete m_window;
 	delete m_device;
