Index: /trunk/nv/gfx/skeletal_mesh.hh
===================================================================
--- /trunk/nv/gfx/skeletal_mesh.hh	(revision 474)
+++ /trunk/nv/gfx/skeletal_mesh.hh	(revision 475)
@@ -11,6 +11,6 @@
 #include <nv/interface/context.hh>
 #include <nv/interface/animated_mesh.hh>
-#include <nv/formats/nmd_loader.hh>
 #include <nv/stl/array.hh>
+#include <nv/gfx/skeleton_instance.hh>
 
 namespace nv
@@ -19,35 +19,23 @@
  	class skeletal_animation_entry : public animation_entry
  	{
- 	public:
- 		skeletal_animation_entry( shash64 name, const mesh_nodes_data* anim, bool a_looping )
+	public:
+		skeletal_animation_entry( shash64 name, const mesh_nodes_data* anim, bool a_looping )
 			: animation_entry( name, a_looping, anim->get_fps(), 0, anim->get_frame_count() )
-			, m_node_data( anim )
+			, m_data( anim )
 		{
-			initialize();
 		}
 
 		skeletal_animation_entry( shash64 name, const mesh_nodes_data* anim, uint32 time_start, uint32 time_end, bool a_looping )
 			: animation_entry( name, a_looping, anim->get_fps(), time_start, time_end )
-			, m_node_data( anim )
+			, m_data( anim )
+
 		{
-			initialize();
 		}
 
 		void prepare( const mesh_nodes_data* bones );
 		void update_skeleton( mat4* tr, uint32 a_ms_time ) const;
-		~skeletal_animation_entry();
-
- 	protected:
-		void initialize();
-		void animate_rec( mat4* data, float time, uint32 node_id, const mat4& parent_mat ) const;
-
+		
 	protected:
-		const mesh_nodes_data* m_node_data;
-		vector< uint32 >* m_children;
-		sint16* m_bone_ids;
-		mat4* m_offsets;
-		uint32 m_root;
-		bool m_prepared;
-		data_descriptor m_interpolation_key;
+		skeleton_instance m_data;
  	};
 
@@ -63,6 +51,5 @@
 		}
 		virtual void update( program a_program );
-		virtual void update_animation( animation_entry* a_anim, uint32 
-			a_anim_time );
+		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;
Index: /trunk/nv/gfx/skeleton_instance.hh
===================================================================
--- /trunk/nv/gfx/skeleton_instance.hh	(revision 475)
+++ /trunk/nv/gfx/skeleton_instance.hh	(revision 475)
@@ -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.
+
+#ifndef NV_GFX_SKELETON_INSTANCE_HH
+#define NV_GFX_SKELETON_INSTANCE_HH
+
+#include <nv/common.hh>
+#include <nv/interface/context.hh>
+#include <nv/interface/mesh_data.hh>
+
+namespace nv
+{
+
+	class skeleton_instance
+	{
+	public:
+		explicit skeleton_instance( const mesh_nodes_data* data )
+			: m_data( data )
+			, m_indices( nullptr )
+			, m_offsets( nullptr )
+		{
+		}
+		void prepare( const mesh_nodes_data* data );
+		void animate( mat4* data, float frame ) const;
+		~skeleton_instance()
+		{
+			delete[] m_indices;
+			delete[] m_offsets;
+		}
+	protected:
+		void animate_rec( mat4* data, float frame, uint32 id, const mat4& parent ) const;
+		void animate_flat( mat4* data, float frame ) const;
+
+		const mesh_nodes_data* m_data;
+		sint16*                m_indices;
+		mat4*                  m_offsets;
+		data_descriptor        m_key;
+	};
+
+}
+
+#endif // NV_GFX_SKELETON_INSTANCE_HH
Index: /trunk/nv/interface/mesh_data.hh
===================================================================
--- /trunk/nv/interface/mesh_data.hh	(revision 474)
+++ /trunk/nv/interface/mesh_data.hh	(revision 475)
@@ -26,4 +26,27 @@
 	class mesh_nodes_data
 	{
+
+		static constexpr size_t MAX_CHILDREN = 7;
+		class child_list : noncopyable
+		{
+
+		public:
+			child_list() : m_size( 0 )
+			{
+				nv::raw_fill_n( m_data, MAX_CHILDREN, 0 );
+			}
+			void push( uint8 id )
+			{
+				NV_ASSERT( m_size < MAX_CHILDREN, "Range error" );
+				m_data[m_size++] = id;
+			}
+			const uint8* begin() const { return m_data; }
+			const uint8* end() const { return m_data + m_size; }
+		private:
+			uint8 m_size;
+			uint8 m_data[MAX_CHILDREN];
+		};
+		static_assert( sizeof( child_list ) == 8, "Align/Padding fail for child_list" );
+
 		friend class mesh_creator;
 		friend class mesh_nodes_creator;
@@ -42,7 +65,25 @@
 		}
 
-		void push_back( data_channel_set* set )
+		void append( data_channel_set* set )
 		{
 			m_data.push_back( set );
+		}
+
+		void initialize()
+		{
+			m_children.resize( m_data.size() );
+			for ( uint8 n = 0; n < uint8( m_data.size() ); ++n )
+			{
+				const data_channel_set* node = m_data[n];
+				if ( node->get_parent_id() != -1 )
+				{
+					m_children[node->get_parent_id()].push( n );
+				}
+			}
+		}
+
+		const child_list& children( size_t i ) const
+		{
+			return m_children[i];
 		}
 
@@ -95,4 +136,5 @@
 	private:
 		vector< data_channel_set* > m_data;
+		vector< child_list >        m_children;
 		shash64 m_name;
 		uint16  m_frame_rate;
Index: /trunk/src/formats/assimp_loader.cc
===================================================================
--- /trunk/src/formats/assimp_loader.cc	(revision 474)
+++ /trunk/src/formats/assimp_loader.cc	(revision 475)
@@ -307,5 +307,5 @@
 					NV_ASSERT( result->size() < MAX_BONES, "Too many bones to merge!" );
 					uint16 index = uint16( result->size() );
-					result->push_back( bone );
+					result->append( bone );
 					names[ bone->get_name() ] = index;
 					translate[b] = index;
@@ -334,4 +334,5 @@
 		}	
 	}
+	result->initialize();
 
 	return result;
@@ -360,6 +361,7 @@
 	for ( auto set : temp_ref )
 	{
-		result->push_back( set );
-	}
+		result->append( set );
+	}
+	result->initialize();
 	delete temp;
 	return result;
Index: /trunk/src/formats/md3_loader.cc
===================================================================
--- /trunk/src/formats/md3_loader.cc	(revision 474)
+++ /trunk/src/formats/md3_loader.cc	(revision 475)
@@ -429,6 +429,7 @@
 		access.set_name( make_name( name ) );
 		load_tags( access.add_channel<md3_key>( uint32( md3->header.num_frames ) ).channel(), name );
-		result->push_back( set );
-	}
+		result->append( set );
+	}
+	result->initialize();
 	return result;
 }
Index: /trunk/src/formats/nmd_loader.cc
===================================================================
--- /trunk/src/formats/nmd_loader.cc	(revision 474)
+++ /trunk/src/formats/nmd_loader.cc	(revision 475)
@@ -107,6 +107,7 @@
 		data_channel_set* set = data_channel_set_creator::create_set( element_header.children );
 		load_channel_set( source, set, element_header );
-		m_node_data->push_back( set );
-	}
+		m_node_data->append( set );
+	}
+	m_node_data->initialize();
 	return true;
 }
Index: /trunk/src/gfx/skeletal_mesh.cc
===================================================================
--- /trunk/src/gfx/skeletal_mesh.cc	(revision 474)
+++ /trunk/src/gfx/skeletal_mesh.cc	(revision 475)
@@ -11,37 +11,5 @@
 #include "nv/stl/unordered_map.hh"
 
-void nv::skeletal_animation_entry::initialize()
-{
-	m_root      = uint32(-1);
-	m_prepared  = false;
-	m_children  = nullptr;
-	m_offsets   = nullptr;
-	m_bone_ids  = new sint16[m_node_data->size()];
-
-	NV_ASSERT( m_node_data, "node data empty!" );
-
-	if ( !m_node_data->is_flat() )
-	{
-		m_children = new vector< uint32 >[m_node_data->size()];
-		for ( uint32 n = 0; n < m_node_data->size(); ++n )
-		{
-			const data_channel_set* node = (*m_node_data)[n];
-			if ( node->get_parent_id() != -1 )
-			{
-				m_children[ node->get_parent_id()].push_back( n );
-			}
-			else
-			{
-				if ( m_root >= 0 )
-				{
-					m_root = uint32( -2 );
-				}
-				else
-					m_root = n;
-			}
-		}
-		NV_ASSERT( m_root != uint32( -1 ), "Animation without root!" );
-	}
-}
+#include "nv/core/logging.hh"
 
 void nv::skeletal_animation_entry::update_skeleton( mat4* data, uint32 a_ms_time ) const
@@ -70,97 +38,10 @@
 	}
 
-	if ( !m_node_data->is_flat() )
-	{
-		if ( m_root == uint32( -2 ) ) // multi-root
-		{
-			for ( uint32 n = 0; n < m_node_data->size(); ++n )
-				if ( ( *m_node_data )[n]->get_parent_id() == -1 )
-					animate_rec( data, fframe, n, mat4() );
-		}
-		else
-			animate_rec( data, fframe, m_root, mat4() );
-		return;
-	}
-
-	for ( uint32 n = 0; n < m_node_data->size(); ++n )
-		if ( m_bone_ids[n] >= 0 )
-		{
-			const data_channel_set* node = (*m_node_data)[n];
-			nv::mat4 node_mat( node->get_transform() );
-
-			if ( node->size() > 0 )
-			{
-				raw_channel_interpolator interpolator( node, m_interpolation_key );
-				node_mat = interpolator.get< mat4 >( fframe );
-			}
-
-			sint16 bone_id = m_bone_ids[n];
-			data[ bone_id ] = node_mat * m_offsets[ bone_id ];
-		}
+	m_data.animate( data, fframe );
 }
 
 void nv::skeletal_animation_entry::prepare( const mesh_nodes_data* bones )
 {
-	if ( m_prepared ) return;
-	nv::hash_store< shash64, uint16 > bone_names;
-	m_offsets = new mat4[ bones->size() ];
-	for ( nv::uint16 bi = 0; bi < bones->size(); ++bi )
-	{
-		const data_channel_set* bone = (*bones)[ bi ];
-		bone_names[ bone->get_name() ] = bi;
-		m_offsets[bi] = bone->get_transform();
-	}
-
-	for ( uint32 n = 0; n < m_node_data->size(); ++n )
-	{
-		const data_channel_set* node = (*m_node_data)[ n ];
-		sint16 bone_id = -1;
-
-		auto bi = bone_names.find( node->get_name() );
-		if ( bi != bone_names.end() )
-		{
-			bone_id = sint16( bi->second );
-		}
-		m_bone_ids[n] = bone_id;
-
-		if ( m_interpolation_key.size() == 0 && node->size() > 0 )
-			m_interpolation_key = node->get_interpolation_key();
-
-	}
-	m_prepared = true;
-}
-
-void nv::skeletal_animation_entry::animate_rec( mat4* data, float time, uint32 node_id, const mat4& parent_mat ) const
-{
-	// TODO: fix transforms, which are now embedded,
-	//       see note in assimp_loader.cc:load_node
-	const data_channel_set* node = ( *m_node_data )[ node_id ];
-	mat4 node_mat( node->get_transform() );
-
-	if ( node->size() > 0 )
-	{
-		raw_channel_interpolator interpolator( node, m_interpolation_key );
-		node_mat = interpolator.get< mat4 >( time );
-	}
-
-	mat4 global_mat = parent_mat * node_mat;
-
-	sint16 bone_id = m_bone_ids[ node_id ];
-	if ( bone_id >= 0 )
-	{
-		data[ bone_id ] = global_mat * m_offsets[ bone_id ];
-	}
-
-	for ( auto child : m_children[ node_id ] )
-	{
-		animate_rec( data, time, child, global_mat );
-	}
-}
-
-nv::skeletal_animation_entry::~skeletal_animation_entry()
-{
-	delete[] m_offsets;
-	delete[] m_children;
-	delete[] m_bone_ids;
+	m_data.prepare( bones );
 }
 
@@ -198,6 +79,4 @@
 nv::transform nv::skeletal_mesh::get_node_transform( uint32 node_id ) const
 {
-	if ( node_id == 0 ) return transform();
-	if ( node_id == uint32(-1) ) return transform( m_transform[0] );
 	return transform( m_transform[ node_id ] );
 }
Index: /trunk/src/gfx/skeleton_instance.cc
===================================================================
--- /trunk/src/gfx/skeleton_instance.cc	(revision 475)
+++ /trunk/src/gfx/skeleton_instance.cc	(revision 475)
@@ -0,0 +1,98 @@
+// 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/skeleton_instance.hh"
+
+void nv::skeleton_instance::prepare( const mesh_nodes_data* bones )
+{
+	if ( m_offsets || m_indices ) return;
+	hash_store< shash64, uint16 > bone_names;
+	m_offsets = new mat4[bones->size()];
+	m_indices = new sint16[m_data->size()];
+
+	for ( nv::uint16 bi = 0; bi < bones->size(); ++bi )
+	{
+		const data_channel_set* bone = ( *bones )[bi];
+		bone_names[bone->get_name()] = bi;
+		m_offsets[bi] = bone->get_transform();
+	}
+
+	for ( uint32 n = 0; n < m_data->size(); ++n )
+	{
+		const data_channel_set* node = ( *m_data )[n];
+		sint16 bone_id = -1;
+
+		auto bi = bone_names.find( node->get_name() );
+		if ( bi != bone_names.end() )
+		{
+			bone_id = sint16( bi->second );
+		}
+		m_indices[n] = bone_id;
+
+		if ( m_key.size() == 0 && node->size() > 0 )
+			m_key = node->get_interpolation_key();
+	}
+}
+
+void nv::skeleton_instance::animate( mat4* data, float frame ) const
+{
+	if ( m_data->is_flat() )
+	{
+		animate_flat( data, frame );
+	}
+	else
+	{
+		for ( uint32 n = 0; n < m_data->size(); ++n )
+			if ( ( *m_data )[n]->get_parent_id() == -1 )
+				animate_rec( data, frame, n, mat4() );
+	}
+}
+
+void nv::skeleton_instance::animate_flat( mat4* data, float frame ) const
+{
+	for ( uint32 n = 0; n < m_data->size(); ++n )
+		if ( m_indices[n] >= 0 )
+		{
+			const data_channel_set* node = ( *m_data )[n];
+			nv::mat4 node_mat( node->get_transform() );
+
+			if ( node->size() > 0 )
+			{
+				raw_channel_interpolator interpolator( node, m_key );
+				node_mat = interpolator.get< mat4 >( frame );
+			}
+
+			sint16 bone_id = m_indices[n];
+			data[bone_id] = node_mat * m_offsets[bone_id];
+		}
+}
+
+void nv::skeleton_instance::animate_rec( mat4* data, float frame, uint32 id, const mat4& parent ) const
+{
+	// TODO: fix transforms, which are now embedded,
+	//       see note in assimp_loader.cc:load_node
+	const data_channel_set* node = ( *m_data )[id];
+	mat4 node_mat( node->get_transform() );
+
+	if ( node->size() > 0 )
+	{
+		raw_channel_interpolator interpolator( node, m_key );
+		node_mat = interpolator.get< mat4 >( frame );
+	}
+
+	mat4 global_mat = parent * node_mat;
+
+	sint16 bone_id = m_indices[id];
+	if ( bone_id >= 0 )
+	{
+		data[bone_id] = global_mat * m_offsets[bone_id];
+	}
+
+	for ( auto child : m_data->children( id ) )
+	{
+		animate_rec( data, frame, child, global_mat );
+	}
+}
