Index: trunk/nv/core/transform.hh
===================================================================
--- trunk/nv/core/transform.hh	(revision 481)
+++ trunk/nv/core/transform.hh	(revision 482)
@@ -76,4 +76,10 @@
 			return m_orientation * v + m_position;
 		}
+
+		transform scaled( float v ) const
+		{
+			return transform( v * m_position, v * m_orientation );
+		}
+
 	private:
 		vec3 m_position;
@@ -92,4 +98,9 @@
 	}
 
+	inline transform operator*( float value, const transform& lhs )
+	{
+		return lhs.scaled( value );
+	}
+
 	template <> struct enum_to_type< TRANSFORM > { typedef transform type; };
 	template <> struct type_to_enum< transform > { static const datatype type = TRANSFORM; };
Index: trunk/nv/formats/assimp_loader.hh
===================================================================
--- trunk/nv/formats/assimp_loader.hh	(revision 481)
+++ trunk/nv/formats/assimp_loader.hh	(revision 482)
@@ -22,15 +22,17 @@
 		explicit assimp_loader( string_table* strings, const string_view& a_ext, uint32 a_assimp_flags = 0 );
 		virtual bool load( stream& source );
-		virtual data_channel_set* release_mesh_data( size_t index = 0 );
+		virtual data_channel_set* release_mesh_data( size_t index, data_node_info& info );
 		virtual size_t get_mesh_count() const { return m_mesh_count; }
 		virtual ~assimp_loader();
 		virtual size_t get_nodes_data_count() const;
 		virtual mesh_nodes_data* release_mesh_nodes_data( size_t index = 0 );
+		virtual data_node_list* release_data_node_list( size_t = 0 );
+		virtual bool is_animated( size_t = 0 );
 		void scene_report() const;
 	private:
-		mesh_nodes_data* release_merged_bones( data_channel_set* meshes );
-		bool load_bones( size_t index, array_ref< data_channel_set* > bones );
-		void load_mesh_data( data_channel_set* data, size_t index );
-		sint16 load_node( uint32 anim_id, array_ref< data_channel_set* > nodes, const void* vnode, sint16 this_id, sint16 parent_id );
+		data_node_list* release_merged_bones( data_channel_set* meshes );
+		bool load_bones( size_t index, array_ref< data_node_info > bones );
+		void load_mesh_data( data_channel_set* data, size_t index, data_node_info& info );
+		sint16 load_node( uint32 anim_id, array_ref< data_channel_set* > nodes, array_ref< data_node_info > infos, const void* vnode, sint16 this_id, sint16 parent_id );
 		uint32 count_nodes( const void* node ) const;
 		data_channel_set* create_keys( const void* vnode );
Index: trunk/nv/formats/nmd_loader.hh
===================================================================
--- trunk/nv/formats/nmd_loader.hh	(revision 481)
+++ trunk/nv/formats/nmd_loader.hh	(revision 482)
@@ -66,5 +66,5 @@
 		uint16 frame_rate;
 		uint16 frame_count;
-		bool   flat;
+		bool   unused;
 	};
 
@@ -78,9 +78,11 @@
 	{
 	public:
-		explicit nmd_loader( string_table* strings ) : mesh_loader( strings ), m_node_data( nullptr ) {}
+		explicit nmd_loader( string_table* strings ) : mesh_loader( strings ), m_node_data( nullptr ), m_bone_data( nullptr ) {}
 		virtual bool load( stream& source );
-		virtual data_channel_set* release_mesh_data( size_t index = 0 );
+		virtual data_channel_set* release_mesh_data( size_t index, data_node_info& info );
 		virtual size_t get_nodes_data_count() const { return 1; }
 		virtual mesh_nodes_data* release_mesh_nodes_data( size_t = 0 );
+		virtual data_node_list* release_data_node_list( size_t = 0 );
+		virtual bool is_animated( size_t = 0 );
 		virtual size_t get_mesh_count() const { return m_meshes.size(); }
 		virtual ~nmd_loader();
@@ -92,7 +94,9 @@
 		bool load_animation( stream& source, const nmd_element_header& e );
 		bool load_channel( stream& source, data_channel_set* channel_set );
-		bool load_channel_set( stream& source, data_channel_set* channel_set, const nmd_element_header& e );
+		bool load_channel_set( stream& source, data_channel_set* channel_set, data_node_info& info, const nmd_element_header& e );
 
 		mesh_nodes_data*            m_node_data;
+		data_node_list*             m_bone_data;
+		vector< data_node_info >    m_infos;
 		vector< data_channel_set* > m_meshes;
 	};
@@ -101,7 +105,9 @@
 	void nmd_dump_header( stream& stream_out, uint32 elements, uint64 name );
 	void nmd_dump_strings( stream& stream_out, const string_table& strings );
-	void nmd_dump_element( stream& stream_out, const data_channel_set& data, nmd_type type );
+	void nmd_dump_element( stream& stream_out, const data_channel_set& data, const data_node_info& info, nmd_type type );
 	void nmd_dump_nodes( stream& stream_out, const mesh_nodes_data& nodes );
-	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_bones( stream& stream_out, const data_node_list& nodes );
+	void nmd_dump( stream& stream_out, array_view< data_channel_set* > meshes, array_view< data_node_info > infos, const nv::data_node_list* nodes, const string_table* strings /*= nullptr*/, uint64 name /*= 0 */ );
+	void nmd_dump( stream& stream_out, array_view< data_channel_set* > meshes, array_view< data_node_info > infos, 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 481)
+++ trunk/nv/formats/obj_loader.hh	(revision 482)
@@ -27,5 +27,5 @@
 		obj_loader( string_table* strings, bool normals = true, bool tangents = false );
 		virtual bool load( stream& source );
-		virtual data_channel_set* release_mesh_data( size_t index = 0 );
+		virtual data_channel_set* release_mesh_data( size_t index, data_node_info& info );
 		virtual size_t get_mesh_count() const { return m_meshes.size(); }
 		~obj_loader();
@@ -35,4 +35,5 @@
 		data_descriptor             m_descriptor;
 		vector< data_channel_set* > m_meshes;
+		vector< data_node_info >    m_infos;
 	};
 
Index: trunk/nv/gfx/mesh_creator.hh
===================================================================
--- trunk/nv/gfx/mesh_creator.hh	(revision 481)
+++ trunk/nv/gfx/mesh_creator.hh	(revision 482)
@@ -71,6 +71,4 @@
 		{}
 		// assumes that keys are equally spaced
-		void pre_transform_keys();
-		// assumes that keys are equally spaced
 		void merge_keys();
 		void transform( float scale, const mat3& r33 );
@@ -79,4 +77,14 @@
 	};
 
+	class data_node_list_creator
+	{
+	public:
+		data_node_list_creator( data_node_list* data ) : m_data( data ) {}
+		void transform( float scale, const mat3& r33 );
+	private:
+		data_node_list* m_data;
+	};
+
+
 }
 
Index: trunk/nv/gfx/skeleton_instance.hh
===================================================================
--- trunk/nv/gfx/skeleton_instance.hh	(revision 481)
+++ trunk/nv/gfx/skeleton_instance.hh	(revision 482)
@@ -54,5 +54,5 @@
 	public:
 		bone_transforms() {}
-		void prepare( const mesh_nodes_data* bone_data );
+		void prepare( const data_node_list& bone_data );
 		uint32 size() const { return m_offsets.size(); };
 	protected:
@@ -66,5 +66,5 @@
 	public:
 		skeleton_binding() {}
-		void prepare( const mesh_nodes_data* node_data, const mesh_nodes_data* bone_data );
+		void prepare( const mesh_nodes_data* node_data, const data_node_list& bone_data );
 		uint32 size() const { return m_indices.size(); };
 		uint32 skeleton_size() const { return m_bone_count; };
@@ -84,15 +84,44 @@
 		const transform* transforms() const { return m_transforms.data(); }
 		size_t size() const { return m_transforms.size(); }
-		void animate( const mesh_nodes_data* node_data, const skeleton_binding& binding, float frame )
+		void animate_local( const mesh_nodes_data* node_data, const skeleton_binding& binding, float frame );
+		void animate( const mesh_nodes_data* node_data, const skeleton_binding& binding, float frame, bool local = false )
 		{
+			if ( local )
+			{
+				animate_local( node_data, binding, frame );
+				return;
+			}
 			if ( m_transforms.size() != binding.skeleton_size() )
 				m_transforms.resize( binding.skeleton_size() );
-			NV_ASSERT( !node_data->is_flat(), "animation data is flat!" );
 			for ( uint32 n = 0; n < node_data->size(); ++n )
-				if ( ( *node_data )[n]->get_parent_id() == -1 )
-					animate_rec( node_data, binding, frame, n, transform() );
+				if ( node_data->get_info(n).parent_id == -1 )
+					animate_rec( node_data, binding, frame, n, transform(), false );
 		}
+		void blend_local( const mesh_nodes_data* node_data, const skeleton_binding& binding, float frame, float blend );
+		void blend( const mesh_nodes_data* node_data, const skeleton_binding& binding, float frame, float blend, bool local = false )
+		{
+			NV_ASSERT( m_transforms.size() == binding.skeleton_size(), "skeleton size wrong!" );
+			if ( local )
+			{
+				blend_local( node_data, binding, frame, blend );
+				return;
+			}
+			for ( uint32 n = 0; n < node_data->size(); ++n )
+				if ( node_data->get_info( n ).parent_id == -1 )
+					blend_rec( node_data, binding, frame, n, transform(), false, blend);
+		}
+
+		void delocalize( const data_node_tree& node_data, const skeleton_binding& binding )
+		{
+			NV_ASSERT( m_transforms.size() == binding.skeleton_size(), "skeleton size wrong!" );
+			for ( uint32 n = 0; n < node_data.size(); ++n )
+				if ( node_data[ n ].parent_id == -1 )
+					delocalize_rec( node_data, binding, n, transform() );
+		}
+
+		void delocalize_rec( const data_node_tree& node_data, const skeleton_binding& binding, uint32 id, const transform& parent );
+		void animate_rec( const mesh_nodes_data* node_data, const skeleton_binding& binding, float frame, uint32 id, const transform& parent, bool local );
+		void blend_rec( const mesh_nodes_data* node_data, const skeleton_binding& binding, float frame, uint32 id, const transform& parent, bool local, float blend );
 	protected:
-		void animate_rec( const mesh_nodes_data* node_data, const skeleton_binding& binding, float frame, uint32 id, const transform& parent );
 		dynamic_array< transform > m_transforms;
 	};
Index: trunk/nv/interface/data_channel.hh
===================================================================
--- trunk/nv/interface/data_channel.hh	(revision 481)
+++ trunk/nv/interface/data_channel.hh	(revision 482)
@@ -14,4 +14,10 @@
 namespace nv
 {
+	struct data_node_info
+	{
+		shash64    name;
+		sint16     parent_id;
+		mat4       transform;
+	};
 
 	struct raw_data_channel
@@ -99,7 +105,4 @@
 				m_channels[c] = move( other.m_channels[c] );
 			m_size       = other.m_size;
-			m_parent_id  = other.m_parent_id;
-			m_transform  = other.m_transform;
-			m_name       = other.m_name;
 			other.m_size = 0;
 		}
@@ -112,7 +115,4 @@
 					m_channels[c] = move( other.m_channels[c] );
 				m_size      = other.m_size;
-				m_parent_id = other.m_parent_id;
-				m_transform = other.m_transform;
-				m_name      = other.m_name;
 				other.m_size = 0;
 			}
@@ -216,22 +216,13 @@
 		const_iterator end()   const { return &m_channels[ m_size ]; }
 
-		shash64 get_name() const { return m_name; }
-		sint16 get_parent_id() const { return m_parent_id; }
-		const mat4& get_transform() const { return m_transform; }
-
 	protected:
 
 		data_channel_set()
 		{
-			m_size      = 0;
-			m_parent_id = -1;
+			m_size           = 0;
 		}
 
 		raw_data_channel m_channels[4];
-
-		uint32  m_size;
-		shash64 m_name;
-		sint16  m_parent_id;
-		mat4    m_transform;
+		uint32           m_size;
 	};
 
Index: trunk/nv/interface/data_channel_access.hh
===================================================================
--- trunk/nv/interface/data_channel_access.hh	(revision 481)
+++ trunk/nv/interface/data_channel_access.hh	(revision 482)
@@ -165,13 +165,4 @@
 		const_iterator begin() const { return m_set->begin(); }
 		const_iterator end()   const { return m_set->end(); }
-
-		void set_transform( const mat4& tr ) { m_set->m_transform = tr; }
-		const mat4& get_transform() const { return m_set->m_transform; }
-
-		void set_name( shash64 name ) {  m_set->m_name = name; }
-		shash64 get_name() const { return m_set->m_name; }
-
-		void set_parent_id( sint16 name ) { m_set->m_parent_id = name; }
-		sint16 get_parent_id() const { return m_set->m_parent_id; }
 	protected:
 		data_channel_set* m_set;
Index: trunk/nv/interface/mesh_data.hh
===================================================================
--- trunk/nv/interface/mesh_data.hh	(revision 481)
+++ trunk/nv/interface/mesh_data.hh	(revision 482)
@@ -21,34 +21,109 @@
 
 	//using mesh_data = data_channel_set;
-
 	// TODO : attribute/property implementation and read/write on every nmd::command
 
-	class mesh_nodes_data
-	{
-
+	class child_list : noncopyable
+	{
 		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" );
-
+	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" );
+
+	class data_node_list
+	{
 		friend class mesh_creator;
 		friend class mesh_nodes_creator;
+		friend class data_node_list_creator;
+	public:
+		typedef vector< data_node_info > storage;
+		typedef storage::const_iterator  const_iterator;
+
+		explicit data_node_list( shash64 name )
+			: m_name( name )
+		{
+		}
+
+		void assign( const data_node_list& other )
+		{
+			m_name = other.m_name;
+			for ( auto node : other )
+				m_data.assign( other.m_data );
+		}
+
+		data_node_list( data_node_list&& other )
+			: m_name( other.m_name ), m_data( nv::move( other.m_data ) )
+		{
+		}
+
+		void append( const data_node_info& set )
+		{
+			m_data.push_back( set );
+		}
+
+		size_t size() const { return m_data.size(); }
+		bool empty() const { return m_data.empty(); }
+
+		const data_node_info& operator[]( size_t i ) const
+		{
+			return m_data[i];
+		}
+
+		const_iterator begin() const { return m_data.begin(); }
+		const_iterator end() const { return m_data.end(); }
+
+		shash64 get_name() const { return m_name; }
+
+	protected:
+		vector< data_node_info > m_data;
+		shash64                  m_name;
+	};
+
+
+	class data_node_tree : public data_node_list
+	{
+		friend class mesh_creator;
+		friend class mesh_nodes_creator;
+		friend class data_node_list_creator;
+	public:
+		
+		explicit data_node_tree( shash64 name ) : data_node_list( name ) {}
+
+		void initialize()
+		{
+			m_children.resize( size() );
+			for ( uint8 n = 0; n < uint8( m_data.size() ); ++n )
+			{
+				if ( m_data[n].parent_id != -1 )
+					m_children[m_data[n].parent_id].push( n );
+			}
+		}
+
+		const child_list& children( size_t i ) const
+		{
+			return m_children[i];
+		}
+
+	protected:
+		vector< child_list >     m_children;
+	};
+
+
+	class mesh_nodes_data
+	{
+		friend class mesh_nodes_creator;
 	public:
 		typedef vector< data_channel_set* > storage;
@@ -56,15 +131,16 @@
 
 		explicit mesh_nodes_data( shash64 name )
-			: m_name( name ), m_frame_rate(0), m_frame_count(0), m_flat( false )
-		{
-		}
-
-		explicit mesh_nodes_data( shash64 name, uint16 a_fps, uint16 a_frames, bool a_flat )
-			: m_name( name ), m_frame_rate(a_fps), m_frame_count(a_frames), m_flat( a_flat )
-		{
-		}
-
-		void append( data_channel_set* set )
-		{
+			: m_info( name ), m_frame_rate(0), m_frame_count(0)
+		{
+		}
+
+		explicit mesh_nodes_data( shash64 name, uint16 a_fps, uint16 a_frames )
+			: m_info( name ), m_frame_rate(a_fps), m_frame_count(a_frames)
+		{
+		}
+
+		void append( data_channel_set* set, data_node_info info )
+		{
+			m_info.append( info );
 			m_data.push_back( set );
 		}
@@ -72,18 +148,10 @@
 		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 );
-				}
-			}
+			m_info.initialize();
 		}
 
 		const child_list& children( size_t i ) const
 		{
-			return m_children[i];
+			return m_info.children(i);
 		}
 
@@ -108,7 +176,8 @@
 		const data_channel_set* get_by_hash( shash64 h ) const
 		{
-			for ( auto data : m_data )
-			{
-				if ( data->get_name() == h ) return data;
+			for ( uint32 i = 0; i < m_info.size(); ++i )
+			{
+				if ( m_info[i].name == h )
+					return m_data[i];
 			}
 			return nullptr;
@@ -119,5 +188,5 @@
 			for ( uint32 i = 0; i < m_data.size(); ++i )
 			{
-				if ( m_data[ i ]->get_name() == h )
+				if ( m_info[ i ].name == h )
 					return int( i );
 			}
@@ -128,4 +197,12 @@
 		{
 			return m_data[i];
+		}
+		const data_node_info& get_info( size_t i ) const
+		{
+			return m_info[i];
+		}
+		const data_node_tree& get_info() const
+		{
+			return m_info;
 		}
 
@@ -133,8 +210,7 @@
 		const_iterator end() const { return m_data.end(); }
 
-		bool is_flat() const { return m_flat; }
 		uint16 get_fps() const { return m_frame_rate; }
 		uint16 get_frame_count() const { return m_frame_count; }
-		shash64 get_name() const { return m_name; }
+		shash64 get_name() const { return m_info.get_name(); }
 
 		~mesh_nodes_data()
@@ -144,10 +220,8 @@
 
 	private:
+		data_node_tree              m_info;
 		vector< data_channel_set* > m_data;
-		vector< child_list >        m_children;
-		shash64 m_name;
 		uint16  m_frame_rate;
 		uint16  m_frame_count;
-		bool    m_flat;
 	};
 
Index: trunk/nv/interface/mesh_loader.hh
===================================================================
--- trunk/nv/interface/mesh_loader.hh	(revision 481)
+++ trunk/nv/interface/mesh_loader.hh	(revision 482)
@@ -31,8 +31,10 @@
 		virtual ~mesh_loader() {}
 		virtual bool load( stream& source ) = 0;
-		virtual data_channel_set* release_mesh_data( size_t index = 0 ) = 0;
+		virtual data_channel_set* release_mesh_data( size_t index, data_node_info& ) = 0;
 		virtual size_t get_mesh_count() const = 0;
 		virtual size_t get_nodes_data_count() const { return 0; }
 		virtual mesh_nodes_data* release_mesh_nodes_data( size_t = 0 ) { return nullptr; }
+		virtual data_node_list* release_data_node_list( size_t = 0 ) { return nullptr; }
+		virtual bool is_animated( size_t = 0 ) { return false; }
 	protected:
 		inline shash64 make_name( const string_view& name )
Index: trunk/nv/stl/hash_map.hh
===================================================================
--- trunk/nv/stl/hash_map.hh	(revision 481)
+++ trunk/nv/stl/hash_map.hh	(revision 482)
@@ -126,5 +126,5 @@
 			iterator it = base_type::find( key );
 			NV_ASSERT_ALWAYS( it != base_type::end(), "Key not found in map!" );
-			return *it;
+			return it->second;
 		}
 
@@ -133,5 +133,5 @@
 			const_iterator it = base_type::find( key );
 			NV_ASSERT_ALWAYS( it != base_type::cend(), "Key not found in map!" );
-			return *it;
+			return it->second;
 		}
 
@@ -142,5 +142,5 @@
 			iterator it = base_type::find( key );
 			NV_ASSERT_ALWAYS( it != base_type::end(), "Key not found in map!" );
-			return *it;
+			return it->second;
 		}
 
@@ -151,5 +151,5 @@
 			const_iterator it = base_type::find( key );
 			NV_ASSERT_ALWAYS( it != base_type::cend(), "Key not found in map!" );
-			return *it;
+			return it->second;
 		}
 
Index: trunk/src/formats/assimp_loader.cc
===================================================================
--- trunk/src/formats/assimp_loader.cc	(revision 481)
+++ trunk/src/formats/assimp_loader.cc	(revision 482)
@@ -105,12 +105,12 @@
 }
 
-data_channel_set* nv::assimp_loader::release_mesh_data( size_t index /*= 0 */ )
+data_channel_set* nv::assimp_loader::release_mesh_data( size_t index, data_node_info& info )
 {
 	if ( index >= m_mesh_count ) return nullptr;
 	data_channel_set* result = data_channel_set_creator::create_set( 2 );
-	load_mesh_data( result, index );
+	load_mesh_data( result, index, info );
 	return result;
 }
-void nv::assimp_loader::load_mesh_data( data_channel_set* data, size_t index )
+void nv::assimp_loader::load_mesh_data( data_channel_set* data, size_t index, data_node_info& info )
 {
 	const aiScene* scene = reinterpret_cast<const aiScene*>( m_scene );
@@ -126,5 +126,6 @@
 	data_channel_set_creator maccess( data );
 	const char* name = mesh->mName.data;
-	maccess.set_name( make_name( name ) );
+	info.name = make_name( name );
+	info.parent_id = -1;
 	uint8*  cdata   = maccess.add_channel( desc, mesh->mNumVertices ).raw_data();
 	uint16* indices = reinterpret_cast<uint16*>( maccess.add_channel< index_u16 >( mesh->mNumFaces * 3 ).raw_data() );
@@ -190,5 +191,5 @@
 }
 
-bool nv::assimp_loader::load_bones( size_t index, array_ref< data_channel_set* > bones )
+bool nv::assimp_loader::load_bones( size_t index, array_ref< data_node_info > bones )
 {
 	if ( m_scene == nullptr ) return false;
@@ -200,9 +201,7 @@
 		aiBone* bone   = mesh->mBones[m];
 		mat4    offset = assimp_mat4_cast( bone->mOffsetMatrix );
-		bones[m] = data_channel_set_creator::create_set( 0 );
-		data_channel_set_creator access( bones[m] );
 		const char* name = bone->mName.data;
-		access.set_name( make_name( name ) );
-		access.set_transform( offset );
+		bones[m].name = make_name( name );
+		bones[m].transform = offset;
 	}
 	return true;
@@ -284,23 +283,24 @@
 }
 
-mesh_nodes_data* nv::assimp_loader::release_merged_bones( data_channel_set* meshes )
-{
-	const aiScene* scene = reinterpret_cast<const aiScene*>( m_scene );
-	mesh_nodes_data* result = new mesh_nodes_data( make_name( "bones" ) );
+data_node_list* nv::assimp_loader::release_merged_bones( data_channel_set* meshes )
+{
+	const aiScene* scene = reinterpret_cast<const aiScene*>( m_scene );
+	data_node_list* result = new data_node_list( make_name( "bones" ) );
 	hash_store< shash64, uint16 > names;
 	for ( unsigned int m = 0; m < m_mesh_count; ++m )
 	{
 		uint16 translate[MAX_BONES];
-		vector< data_channel_set* > bones;
+		vector< data_node_info > bones;
 		const aiMesh*  mesh  = scene->mMeshes[ m ];
 		if ( mesh->mNumBones != 0 )
 		{
 			bones.resize( mesh->mNumBones );
+			NV_ASSERT( false, "parent ids for bones are not loaded!" );
 			load_bones( m, bones );
 			for ( unsigned int b = 0; b < mesh->mNumBones; ++b )
 			{
 
-				data_channel_set* bone = bones[b];
-				auto iname = names.find( bone->get_name() );
+				data_node_info bone = bones[b];
+				auto iname = names.find( bone.name );
 				if ( iname == names.end() )
 				{
@@ -308,5 +308,5 @@
 					uint16 index = uint16( result->size() );
 					result->append( bone );
-					names[ bone->get_name() ] = index;
+					names[ bone.name ] = index;
 					translate[b] = index;
 				}
@@ -334,5 +334,5 @@
 		}	
 	}
-	result->initialize();
+	//result->initialize();
 
 	return result;
@@ -352,18 +352,39 @@
 	uint16 frame_rate     = static_cast<uint16>( anim->mTicksPerSecond );
 	uint16 duration       = static_cast<uint16>( anim->mDuration );
-	bool   flat           = false;
-
-	data_channel_set** temp = new data_channel_set*[ count ];
+
+	data_channel_set** temp  = new data_channel_set*[ count ];
+	data_node_info*    temp2 = new data_node_info[count];
 	array_ref< data_channel_set* > temp_ref( temp, count );
-	load_node( index, temp_ref, root, 0, -1 );
-
-	mesh_nodes_data* result = new mesh_nodes_data( make_name( static_cast<const char*>( anim->mName.data ) ), frame_rate, duration, flat );
-	for ( auto set : temp_ref )
-	{
-		result->append( set );
+	array_ref< data_node_info >    temp2_ref( temp2, count );
+	load_node( index, temp_ref, temp2_ref, root, 0, -1 );
+
+	mesh_nodes_data* result = new mesh_nodes_data( make_name( static_cast<const char*>( anim->mName.data ) ), frame_rate, duration );
+	for ( nv::uint32 i = 0; i < count; ++i )
+	{
+		result->append( temp_ref[i], temp2_ref[i] );
 	}
 	result->initialize();
 	delete temp;
+	delete temp2;
 	return result;
+}
+
+data_node_list* nv::assimp_loader::release_data_node_list( size_t index /*= 0 */ )
+{
+	int this_is_incorrect;
+	NV_ASSERT( false, "unimplemented!" );
+// 	mesh_nodes_data* half_result = release_mesh_nodes_data( index );
+// 	data_node_list* result = new data_node_list( half_result->get_name() );
+// 	for ( auto node : *half_result )
+// 		result->append( node->get_info() );
+// 	delete half_result;
+//	return result;
+	return nullptr;
+}
+
+bool nv::assimp_loader::is_animated( size_t /*= 0 */ )
+{
+	int this_is_incorrect;
+	return false;
 }
 
@@ -379,5 +400,5 @@
 }
 
-nv::sint16 nv::assimp_loader::load_node( uint32 anim_id, array_ref< data_channel_set* > nodes, const void* vnode, sint16 this_id, sint16 parent_id )
+nv::sint16 nv::assimp_loader::load_node( uint32 anim_id, array_ref< data_channel_set* > nodes, array_ref< data_node_info > infos, const void* vnode, sint16 this_id, sint16 parent_id )
 {
 	const aiScene* scene = reinterpret_cast<const aiScene*>( m_scene );
@@ -396,18 +417,18 @@
 	nodes[ this_id ] = anode ? create_keys( anode ) : data_channel_set_creator::create_set( 0 );
 
-	data_channel_set_creator access( nodes[this_id] );
-	access.set_name( make_name( name ) );
-	access.set_parent_id( parent_id );
+	infos[this_id].name      = make_name( name );
+	infos[this_id].parent_id = parent_id;
 	// This value is ignored by the create_transformed_keys, but needed by create_direct_keys!
 	// TODO: find a common solution!
 	//       This is bad because create_transformed_keys never uses node-transformations for
 	//       node's without keys
-	access.set_transform( nv::assimp_mat4_cast( node->mTransformation ) );
-	if ( this_id == 0 ) access.set_transform( mat4() );
+	// TODO: this can probably be deleted
+	infos[this_id].transform = nv::assimp_mat4_cast( node->mTransformation );
+	if ( this_id == 0 ) infos[this_id].transform = mat4();
 
 	nv::sint16 next = this_id + 1;
 	for ( unsigned i = 0; i < node->mNumChildren; ++i )
 	{
-		next = load_node( anim_id, nodes, node->mChildren[i], next, this_id );
+		next = load_node( anim_id, nodes, infos, node->mChildren[i], next, this_id );
 	}
 
Index: trunk/src/formats/nmd_loader.cc
===================================================================
--- trunk/src/formats/nmd_loader.cc	(revision 481)
+++ trunk/src/formats/nmd_loader.cc	(revision 482)
@@ -37,13 +37,16 @@
 {
 	data_channel_set* mesh = data_channel_set_creator::create_set( e.children );
-	load_channel_set( source, mesh, e );
+	data_node_info info;
+	load_channel_set( source, mesh, info, e );
 //	m_mesh_names.push_back( e.name );
+	m_infos.push_back( info );
 	m_meshes.push_back( mesh );
 	return true;
 }
 
-data_channel_set* nv::nmd_loader::release_mesh_data( size_t index )
+data_channel_set* nv::nmd_loader::release_mesh_data( size_t index, data_node_info& info )
 {
 	data_channel_set* result = m_meshes[ index ];
+	info = m_infos[ index ];
 	m_meshes[ index ] = nullptr;
 	return result;
@@ -54,7 +57,9 @@
 	for ( auto mesh : m_meshes ) if ( mesh ) delete mesh;
 	if ( m_node_data ) delete m_node_data;
+	if ( m_bone_data ) delete m_bone_data;
 	m_meshes.clear();
 
-	m_node_data  = nullptr;
+	m_node_data = nullptr;
+	m_bone_data = nullptr;
 }
 
@@ -85,5 +90,6 @@
 	nmd_animation_header animation_header;
 	source.read( &animation_header, sizeof( animation_header ), 1 );
-	m_node_data = new mesh_nodes_data( e.name, animation_header.frame_rate, animation_header.frame_count, animation_header.flat );
+	m_node_data = new mesh_nodes_data( e.name, animation_header.frame_rate, animation_header.frame_count );
+	m_bone_data = new data_node_list( e.name );
 	for ( uint32 i = 0; i < e.children; ++i )
 	{
@@ -93,6 +99,8 @@
 		NV_ASSERT( element_header.type == nmd_type::NODE, "NODE expected!" );
 		data_channel_set* set = data_channel_set_creator::create_set( element_header.children );
-		load_channel_set( source, set, element_header );
-		m_node_data->append( set );
+		data_node_info info;
+		load_channel_set( source, set, info, element_header );
+		m_bone_data->append( info );
+		m_node_data->append( set, info );
 	}
 	m_node_data->initialize();
@@ -110,5 +118,5 @@
 }
 
-bool nv::nmd_loader::load_channel_set( stream& source, data_channel_set* channel_set, const nmd_element_header& e )
+bool nv::nmd_loader::load_channel_set( stream& source, data_channel_set* channel_set, data_node_info& info, const nmd_element_header& e )
 {
 	data_channel_set_creator kaccess( channel_set );
@@ -117,8 +125,7 @@
 		load_channel( source, channel_set );
 	}
-	data_channel_set_creator access( channel_set );
-	access.set_name( e.name );
-	access.set_parent_id( e.parent_id );
-	access.set_transform( e.transform );
+	info.name = e.name;
+	info.parent_id = e.parent_id;
+	info.transform = e.transform;
 	return true;
 }
@@ -126,11 +133,20 @@
 mesh_nodes_data* nv::nmd_loader::release_mesh_nodes_data( size_t )
 {
-	if ( m_node_data )
-	{
-		mesh_nodes_data* result = m_node_data;
-		m_node_data = nullptr;
-		return result;
-	}
-	return nullptr;
+	mesh_nodes_data* result = m_node_data;
+	m_node_data = nullptr;
+	return result;
+}
+
+data_node_list* nv::nmd_loader::release_data_node_list( size_t )
+{
+	data_node_list* result = m_bone_data;
+	m_bone_data = nullptr;
+	return result;
+}
+
+bool nv::nmd_loader::is_animated( size_t /*= 0 */ )
+{
+	if ( !m_node_data ) return false;
+	return m_node_data->is_animated();
 }
 
@@ -150,5 +166,5 @@
 }
 
-void nv::nmd_dump_element( stream& stream_out, const data_channel_set& data, nmd_type type )
+void nv::nmd_dump_element( stream& stream_out, const data_channel_set& data, const data_node_info& info, nmd_type type )
 {
 	uint32 size = 0;
@@ -163,7 +179,7 @@
 	eheader.children   = static_cast<uint16>( data.size() );
 	eheader.size       = size;
-	eheader.name       = data.get_name();
-	eheader.transform  = data.get_transform();
-	eheader.parent_id  = data.get_parent_id();
+	eheader.name       = info.name;
+	eheader.transform  = info.transform;
+	eheader.parent_id  = info.parent_id;
 	eheader.attributes = 0;
 	stream_out.write( &eheader, sizeof( eheader ), 1 );
@@ -205,10 +221,49 @@
 	aheader.frame_rate  = nodes.get_fps();
 	aheader.frame_count = nodes.get_frame_count();
-	aheader.flat = nodes.is_flat();
+	aheader.unused      = false;
 	stream_out.write( &aheader, sizeof( aheader ), 1 );
 
+	for ( uint32 i = 0; i < nodes.size(); ++i )
+	{
+		nmd_dump_element( stream_out, *nodes[i], nodes.get_info(i), nv::nmd_type::NODE );
+	}
+}
+
+void nv::nmd_dump_bones( stream& stream_out, const data_node_list& nodes )
+{
+	uint32 total = sizeof( nmd_animation_header );
 	for ( auto node : nodes )
 	{
-		nmd_dump_element( stream_out, *node, nv::nmd_type::NODE );
+		total += sizeof( nmd_element_header );
+	}
+
+	nmd_element_header header;
+	header.type = nmd_type::ANIMATION;
+	header.children = static_cast<uint16>( nodes.size() );
+	header.size = total;
+	header.name = nodes.get_name();
+	header.transform = mat4();
+	header.parent_id = -1;
+	header.attributes = 0;
+
+	stream_out.write( &header, sizeof( header ), 1 );
+
+	nmd_animation_header aheader;
+	aheader.frame_rate = 0;
+	aheader.frame_count = 0;
+	aheader.unused = false;
+	stream_out.write( &aheader, sizeof( aheader ), 1 );
+
+	for ( auto node : nodes )
+	{
+		nmd_element_header eheader;
+		eheader.type = nv::nmd_type::NODE;
+		eheader.children = 0;
+		eheader.size = 0;
+		eheader.name = node.name;
+		eheader.transform = node.transform;
+		eheader.parent_id = node.parent_id;
+		eheader.attributes = 0;
+		stream_out.write( &eheader, sizeof( eheader ), 1 );
 	}
 }
@@ -227,5 +282,5 @@
 }
 
-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 */ )
+void nv::nmd_dump( stream& stream_out, array_view< data_channel_set* > meshes, array_view< data_node_info > infos, const mesh_nodes_data* nodes, const string_table* strings /*= nullptr*/, uint64 name /*= 0 */ )
 {
 	uint32 elements = ( strings ? 1 : 0 ) // +1 string array
@@ -237,5 +292,5 @@
 	{
 		NV_ASSERT( meshes[i], "mesh is null!" );
-		nmd_dump_element( stream_out, *meshes[i], nv::nmd_type::MESH );
+		nmd_dump_element( stream_out, *meshes[i], infos[i], nv::nmd_type::MESH );
 	}
 
@@ -243,4 +298,28 @@
 	{
 		nmd_dump_nodes( stream_out, *nodes );
+	}
+
+	if ( strings )
+	{
+		nmd_dump_strings( stream_out, *strings );
+	}
+}
+
+void nv::nmd_dump( stream& stream_out, array_view< data_channel_set* > meshes, array_view< data_node_info > infos, const nv::data_node_list* nodes, const string_table* strings /*= nullptr*/, uint64 name /*= 0 */ )
+{
+	uint32 elements = ( strings ? 1 : 0 ) // +1 string array
+		+ meshes.size() // meshes
+		+ ( nodes && nodes->size() > 0 ? 1 : 0 ); // nodes
+	nmd_dump_header( stream_out, elements, name );
+
+	for ( uint32 i = 0; i < meshes.size(); ++i )
+	{
+		NV_ASSERT( meshes[i], "mesh is null!" );
+		nmd_dump_element( stream_out, *meshes[i], infos[i], nv::nmd_type::MESH );
+	}
+
+	if ( nodes && nodes->size() > 0 )
+	{
+		nmd_dump_bones( stream_out, *nodes );
 	}
 
Index: trunk/src/formats/obj_loader.cc
===================================================================
--- trunk/src/formats/obj_loader.cc	(revision 481)
+++ trunk/src/formats/obj_loader.cc	(revision 482)
@@ -327,7 +327,6 @@
 	
 		data_channel_set* result = data_channel_set_creator::create_set( 1 );
-		data_channel_set_creator raccess( result );
-		raccess.set_name( make_name( reader->name ) );
-		uint8* rdata = raccess.add_channel( m_descriptor, reader->size * 3 ).raw_data();
+
+		uint8* rdata = data_channel_set_creator(result).add_channel( m_descriptor, reader->size * 3 ).raw_data();
 
 		if ( reader->raw_size() > 0 )
@@ -335,5 +334,8 @@
 			raw_copy_n( reader->raw_pointer(), reader->raw_size(), rdata );
 		}
-
+		data_node_info info;
+		info.name = make_name( reader->name );
+		info.parent_id = -1;
+		m_infos.push_back( info );
 		m_meshes.push_back( result );
 
@@ -345,7 +347,8 @@
 }
 
-data_channel_set* nv::obj_loader::release_mesh_data( size_t index )
+data_channel_set* nv::obj_loader::release_mesh_data( size_t index, data_node_info& info )
 {
 	data_channel_set* result = m_meshes[ index ];
+	info = m_infos[index];
 	m_meshes[ index ] = nullptr;
 	return result;
Index: trunk/src/gfx/mesh_creator.cc
===================================================================
--- trunk/src/gfx/mesh_creator.cc	(revision 481)
+++ trunk/src/gfx/mesh_creator.cc	(revision 482)
@@ -13,81 +13,4 @@
 struct nv_key_transform { nv::transform tform; };
 
-void nv::mesh_nodes_creator::pre_transform_keys()
-{
-	if ( m_data->m_flat ) return;
-	merge_keys();
-	uint16 max_frames = 0;
-
-	nv::vector< sint16 > ids;
-	{
-		// TODO: simplify this shit!
-		// The complexity here is that we cannot pre-transform in any order
-		// as the bones depend on previous bones, but ARE NOT IN ORDER
-		// 
-		// Either rewrite this a lot nicer, or sort the bones on creation
-		// by tree-order.
-
-		ids.reserve( m_data->m_data.size() );
-		{
-			nv::vector< sint16 > ids_next;
-			ids_next.reserve( m_data->m_data.size() );
-			ids_next.push_back( -1 );
-			while ( !ids_next.empty() )
-			{
-				sint16 pid = ids_next.back();
-				ids_next.pop_back();
-				for ( sint16 i = 0; i < sint16(m_data->m_data.size()); ++i )
-					if ( m_data->m_data[i]->get_parent_id() == pid )
-					{
-						sint16* it = nv::find( ids.begin(), ids.end(), i );
-						if ( it == ids.end() )
-						{
-							ids.push_back( i );
-							ids_next.push_back( i );
-						}
-					}
-			}
-		}
-
-		if ( ids.size() != m_data->m_data.size() )
-		{
-			NV_LOG_WARNING( "Bad skeleton!" );
-		}
-	}
-
-	NV_LOG_DEBUG( "ID/PID" );
-	for ( auto id : ids )
-	{
-		data_channel_set* keys = m_data->m_data[id];
-		sint16 parent_id = keys->get_parent_id();
-		NV_LOG_DEBUG( "Id : ", id, " PID", parent_id );
-		data_channel_set* pkeys = ( parent_id != -1 ? m_data->m_data[parent_id] : nullptr );
-		size_t count     = ( keys ? keys->get_channel_size(0) : 0 );
-		size_t pcount    = ( pkeys ? pkeys->get_channel_size(0) : 0 );
-		max_frames = nv::max<uint16>( uint16( count ), max_frames );
-		if ( pkeys && pkeys->size() > 0 && keys && keys->size() > 0 )
-		{
-			data_channel_access< nv_key_transform > channel_creator( keys, 0 );
-
-			nv_key_transform* channel = channel_creator.data();
-			const nv_key_transform* pchannel = pkeys->get_channel(0)->data_cast< nv_key_transform >();
-			for ( unsigned n = 0; n < count; ++n )
-			{
-				channel[n].tform = pchannel[ nv::min( n, pcount-1 ) ].tform * channel[n].tform;
-			}
-		}
-	}
-
-	// DAE pre_transform hack
-	if ( m_data->m_frame_rate == 1 )
-	{
-		m_data->m_frame_rate  = 32;
-		m_data->m_frame_count = max_frames;
-	}
-
-	m_data->m_flat = true;
-}
-
-
 void nv::mesh_nodes_creator::merge_keys()
 {
@@ -110,7 +33,4 @@
 			data_channel_set* new_keys = data_channel_set_creator::create_set( 1 );
 			data_channel_set_creator nk_access( new_keys );
-			nk_access.set_name( old_keys->get_name() );
-			nk_access.set_parent_id( old_keys->get_parent_id() );
-			nk_access.set_transform( old_keys->get_transform() );
 			data_channel_access< nv_key_transform > kt_channel( nk_access.add_channel<nv_key_transform>( max_keys ) );
 
@@ -145,6 +65,4 @@
 	for ( auto node : m_data->m_data )
 	{
-		node->m_transform = pre_transform * node->m_transform * post_transform;
-
 		for ( size_t c = 0; c < node->size(); ++c )
 		{
@@ -158,4 +76,15 @@
 	}
 }
+
+void nv::data_node_list_creator::transform( float scale, const mat3& r33 )
+{
+	mat3 ri33 = math::inverse( r33 );
+	mat4 pre_transform( scale * r33 );
+	mat4 post_transform( 1.f / scale * ri33 );
+
+	for ( auto& node : m_data->m_data )
+		node.transform = pre_transform * node.transform * post_transform;
+}
+
 
 void nv::mesh_data_creator::transform( float scale, const mat3& r33 )
@@ -209,4 +138,5 @@
 	nv::vec4 tangent;
 };
+
 
 void nv::mesh_data_creator::flip_normals()
@@ -434,4 +364,5 @@
 }
 
+
 template < typename T >
 static inline void swap_culling_impl( nv::raw_data_channel* index_channel )
@@ -650,3 +581,2 @@
 	initialize();
 }
-
Index: trunk/src/gfx/skeleton_instance.cc
===================================================================
--- trunk/src/gfx/skeleton_instance.cc	(revision 481)
+++ trunk/src/gfx/skeleton_instance.cc	(revision 482)
@@ -9,5 +9,5 @@
 #include "nv/core/profiler.hh"
 
-void nv::skeleton_binding::prepare( const mesh_nodes_data* node_data, const mesh_nodes_data* bone_data )
+void nv::skeleton_binding::prepare( const mesh_nodes_data* node_data, const data_node_list& bone_data )
 {
 	if ( m_indices.empty() )
@@ -16,17 +16,12 @@
 		hash_store< shash64, uint16 > bone_names;
 		m_indices.resize( node_data->size() );
-		
-		for ( nv::uint16 bi = 0; bi < bone_data->size(); ++bi )
-		{
-			const data_channel_set* bone = ( *bone_data )[bi];
-			bone_names[bone->get_name()] = bi;
-		}
+
+		for ( nv::uint16 bi = 0; bi < bone_data.size(); ++bi )
+			bone_names[bone_data[bi].name] = bi;
 
 		for ( uint32 n = 0; n < node_data->size(); ++n )
 		{
-			const data_channel_set* node = ( *node_data )[n];
 			sint16 bone_id = -1;
-
-			auto bi = bone_names.find( node->get_name() );
+			auto bi = bone_names.find( node_data->get_info( n ).name );
 			if ( bi != bone_names.end() )
 			{
@@ -36,5 +31,5 @@
 
 		}
-		m_bone_count = bone_data->size();
+		m_bone_count = bone_data.size();
 	}
 
@@ -48,69 +43,5 @@
 			}
 	}
-
-
 }
-
-// void nv::skeleton_instance::animate_( const mesh_nodes_data* node_data, const skeleton_binding& binding, float frame )
-// {
-// 	if ( m_matrix.size() > 0 )
-// 	{
-// 		if ( node_data->is_flat() )
-// 		{
-// 			animate_flat( node_data, binding, frame );
-// 		}
-// 		else
-// 		{
-// 			for ( uint32 n = 0; n < node_data->size(); ++n )
-// 				if ( ( *node_data )[n]->get_parent_id() == -1 )
-// 					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 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];
-// 	transform node_mat( node->get_transform() );
-// 
-// 	if ( node->size() > 0 )
-// 	{
-// 		raw_channel_interpolator interpolator( node, binding.m_key );
-// 		node_mat = interpolator.get< transform >( frame );
-// 	}
-// 
-// 	transform global_mat = parent * node_mat;
-// 
-//  	sint16 bone_id = binding.m_indices[id];
-//  	if ( bone_id >= 0 )
-//  	{
-//  		m_matrix[bone_id] = global_mat.extract() * binding.m_offsets[bone_id];
-//  	}
-// 
-// 	for ( auto child : node_data->children( id ) )
-// 	{
-// 		animate_rec( node_data, binding, frame, child, global_mat );
-// 	}
-// }
-// 
-// void nv::skeleton_instance::animate_flat( const mesh_nodes_data* node_data, const skeleton_binding& binding, float frame ) 
-// {
-// 	for ( uint32 n = 0; n < node_data->size(); ++n )
-// 		if ( binding.m_indices[n] >= 0 )
-// 		{
-// 			const data_channel_set* node = ( *node_data )[n];
-// 			nv::mat4 node_mat( node->get_transform() );
-// 
-// 			if ( node->size() > 0 )
-// 			{
-// 				raw_channel_interpolator interpolator( node, binding.m_key );
-// 				node_mat = interpolator.get< mat4 >( frame );
-// 			}
-// 			sint16 bone_id = binding.m_indices[n];
-// 			m_matrix[bone_id] = node_mat * binding.m_offsets[bone_id];
-// 		}
-// }
 
 void nv::skeleton_instance::assign( const skeleton_transforms& skeleton, const bone_transforms& bones )
@@ -120,5 +51,7 @@
 	const transform* transforms = skeleton.transforms();
 	for ( uint32 n = 0; n < skeleton.size(); ++n )
+	{
 		m_matrix[n] = transforms[n].extract() * bones.m_offsets[n];
+	}
 }
 
@@ -129,8 +62,82 @@
 }
 
-void nv::skeleton_transforms::animate_rec( const mesh_nodes_data* node_data, const skeleton_binding& binding, float frame, uint32 id, const transform& parent )
+void nv::skeleton_transforms::animate_local( const mesh_nodes_data* node_data, const skeleton_binding& binding, float frame )
+{
+	if ( m_transforms.size() != binding.skeleton_size() )
+		m_transforms.resize( binding.skeleton_size() );
+	for ( uint32 n = 0; n < node_data->size(); ++n )
+	{
+		const data_channel_set* node = ( *node_data )[n];
+		sint16 bone_id = binding.m_indices[n];
+		if ( bone_id >= 0 )
+		{
+			if ( node->size() > 0 )
+				m_transforms[bone_id] = raw_channel_interpolator( node, binding.m_key ).get< transform >( frame );
+			int confirm_that_not_needed;
+// 			else
+// 				m_transforms[bone_id] = transform( node->get_transform() );
+		}
+	}
+}
+
+void nv::skeleton_transforms::blend_local( const mesh_nodes_data* node_data, const skeleton_binding& binding, float frame, float blend )
+{
+	if ( m_transforms.size() != binding.skeleton_size() )
+		m_transforms.resize( binding.skeleton_size() );
+	for ( uint32 n = 0; n < node_data->size(); ++n )
+	{
+		const data_channel_set* node = ( *node_data )[n];
+		sint16 bone_id = binding.m_indices[n];
+		if ( bone_id >= 0 )
+		{
+			
+			transform tr = node->size() > 0 ? raw_channel_interpolator( node, binding.m_key ).get< transform >( frame ) : transform( /*node->get_transform()*/ ); int confirm_that_not_needed;
+			m_transforms[bone_id] = nv::interpolate( m_transforms[bone_id], tr, blend );
+		}
+	}
+}
+
+void nv::skeleton_transforms::delocalize_rec( const data_node_tree& node_data, const skeleton_binding& binding, uint32 id, const transform& parent )
+{
+	sint16 bone_id = binding.m_indices[id];
+	transform global_mat = parent;
+	if ( bone_id >= 0 )
+	{
+		global_mat *= m_transforms[bone_id];
+		m_transforms[bone_id] = global_mat;
+	}
+	for ( auto child : node_data.children( id ) )
+	{
+		delocalize_rec( node_data, binding, child, global_mat );
+	}
+}
+
+void nv::skeleton_transforms::animate_rec( const mesh_nodes_data* node_data, const skeleton_binding& binding, float frame, uint32 id, const transform& parent, bool local )
 {
 	const data_channel_set* node = ( *node_data )[id];
-	transform node_mat( node->get_transform() );
+	transform node_mat;
+
+	if ( node->size() > 0 )
+		node_mat = raw_channel_interpolator( node, binding.m_key ).get< transform >( frame );
+	int confirm_that_not_needed;
+	// 	else
+// 		node_mat = transform( node->get_transform() );
+	sint16 bone_id = binding.m_indices[id];
+	transform global_mat = parent * node_mat;
+	if ( bone_id >= 0 )
+	{
+		m_transforms[bone_id] = local ? node_mat : global_mat;
+	}
+	for ( auto child : node_data->children( id ) )
+	{
+		animate_rec( node_data, binding, frame, child, global_mat, local );
+	}
+}
+
+void nv::skeleton_transforms::blend_rec( const mesh_nodes_data* node_data, const skeleton_binding& binding, float frame, uint32 id, const transform& parent, bool local, float blend )
+{
+	const data_channel_set* node = ( *node_data )[id];
+	int confirm_that_not_needed;
+	transform node_mat/*( node->get_transform() )*/;
 
 	if ( node->size() > 0 )
@@ -143,23 +150,21 @@
 	if ( bone_id >= 0 )
 	{
-		m_transforms[bone_id] = global_mat;
+		m_transforms[bone_id] = nv::interpolate( m_transforms[bone_id], local ? node_mat : global_mat, blend );
 	}
 	for ( auto child : node_data->children( id ) )
 	{
-		animate_rec( node_data, binding, frame, child, global_mat );
+		blend_rec( node_data, binding, frame, child, global_mat, local, blend );
 	}
 }
 
-void nv::bone_transforms::prepare( const mesh_nodes_data* bone_data )
+
+void nv::bone_transforms::prepare( const data_node_list& bone_data )
 {
 	if ( m_offsets.empty() )
 	{
-		m_offsets.resize( bone_data->size() );
+		m_offsets.resize( bone_data.size() );
 
-		for ( nv::uint16 bi = 0; bi < bone_data->size(); ++bi )
-		{
-			const data_channel_set* bone = ( *bone_data )[bi];
-			m_offsets[bi] = bone->get_transform();
-		}
+		for ( nv::uint16 bi = 0; bi < bone_data.size(); ++bi )
+			m_offsets[bi] = bone_data[bi].transform;
 	}
 }
