Index: /trunk/nv/formats/assimp_loader.hh
===================================================================
--- /trunk/nv/formats/assimp_loader.hh	(revision 248)
+++ /trunk/nv/formats/assimp_loader.hh	(revision 249)
@@ -11,4 +11,5 @@
 #include <nv/interface/mesh_loader.hh>
 #include <nv/interface/mesh_data.hh>
+#include <nv/interface/animated_mesh.hh>
 
 namespace nv 
@@ -29,4 +30,29 @@
 	};
 
+	struct assimp_animated_node_data
+	{
+		std::string           name;
+		sint32                parent_id;
+		mat4                  transform;
+		key_animation_data*   keys;
+		sint32                bone_id;   // reconstructed
+		std::vector< assimp_animated_node_data* > children;  // reconstructed
+
+		assimp_animated_node_data() : name(), parent_id( -1 ), keys( nullptr ), bone_id( -1 ) {}
+		~assimp_animated_node_data() 
+		{
+			if ( keys ) delete keys;
+		}
+	};
+
+	struct assimp_animation
+	{
+		float fps;
+		float duration;
+		bool  pretransformed;
+		std::vector< assimp_animated_node_data > nodes; // read-only!
+	};
+
+
 	class assimp_loader : public mesh_loader
 	{
@@ -39,7 +65,12 @@
 		virtual ~assimp_loader();
 		assimp_model* release_merged_model();
+		assimp_animation* release_animation( size_t index, bool pre_transform, const std::vector< assimp_bone >* bone_data );
 		bool load_bones( size_t index, std::vector< assimp_bone >& bones );
 		void scene_report() const;
 	private:
+		uint32 load_node( assimp_animation* data, const void* vnode, sint32 this_id, sint32 parent_id );
+		uint32 count_nodes( const void* node ) const;
+		key_animation_data* create_transformed_keys( const void* vnode, const key_animation_data* parent_keys );
+		key_animation_data* create_direct_keys( const void* vnode );
 
 		string m_ext;
Index: /trunk/nv/gfx/animation.hh
===================================================================
--- /trunk/nv/gfx/animation.hh	(revision 248)
+++ /trunk/nv/gfx/animation.hh	(revision 249)
@@ -79,4 +79,5 @@
 		virtual void dump( stream* out_stream ) = 0;
 		virtual void load( stream* in_stream ) = 0;
+		virtual bool empty() const = 0;
 		virtual ~key_animation_data() {}
 	};
@@ -134,4 +135,5 @@
 		void reserve( size_t sz ) { m_keys.reserve( sz ); }
 		void insert( const transform& t ) { m_keys.push_back( t ); }
+		bool empty() const { return m_keys.empty(); }
 		size_t size() const { return m_keys.size(); }
 		const transform& get( size_t index ) const { return m_keys[ index ]; }
Index: /trunk/src/formats/assimp_loader.cc
===================================================================
--- /trunk/src/formats/assimp_loader.cc	(revision 248)
+++ /trunk/src/formats/assimp_loader.cc	(revision 249)
@@ -270,2 +270,158 @@
 }
 
+assimp_animation* nv::assimp_loader::release_animation( size_t, bool pre_transform, const std::vector< assimp_bone >* bone_data )
+{
+	if ( m_scene == nullptr ) return nullptr;
+	const aiScene* scene = (const aiScene*)m_scene;
+	if ( scene->mRootNode == nullptr || scene->mAnimations[0] == nullptr ) return nullptr;
+	assimp_animation* result = new assimp_animation;
+
+	// need resize not to copy!
+	result->nodes.resize( count_nodes( scene->mRootNode ) );
+
+	const aiNode*      root = scene->mRootNode;
+	const aiAnimation* anim = scene->mAnimations[0]; // if implemented, change in load_node also
+
+	result->fps            = (float)anim->mTicksPerSecond;
+	result->duration       = (float)anim->mDuration;
+	result->pretransformed = pre_transform;
+
+	load_node( result, root, 0, -1 );
+	result->nodes[0].transform = glm::scale( m_scale, m_scale, m_scale ) * result->nodes[0].transform;
+
+	if ( bone_data )
+	{
+		std::unordered_map< std::string, uint16 > names;
+		for ( uint16 bi = 0; bi < bone_data->size(); ++bi )
+		{
+			names[ (*bone_data)[bi].name ] = bi;
+		}
+
+		for ( unsigned i = 0; i < result->nodes.size(); ++i )
+		{
+			assimp_animated_node_data& node = result->nodes[i];
+			node.bone_id = -1;
+			auto bi = names.find( node.name );
+			if ( bi != names.end() )
+			{
+				node.bone_id = bi->second;
+			}
+			if ( node.parent_id != -1 ) 
+			{
+				result->nodes[ node.parent_id ].children.push_back( &node );
+			}
+		}
+	}
+
+	return result;
+}
+
+nv::uint32 nv::assimp_loader::count_nodes( const void* node ) const
+{
+	const aiNode* ainode = (const aiNode*)node;
+	nv::uint32 count = 1;
+	for ( unsigned i = 0; i < ainode->mNumChildren; ++i )
+	{
+		count += count_nodes( ainode->mChildren[i] );
+	}
+	return count;
+}
+
+nv::uint32 nv::assimp_loader::load_node( assimp_animation* data, const void* vnode, sint32 this_id, sint32 parent_id )
+{
+	const aiScene* scene = (const aiScene*)m_scene;
+	const aiNode*  node  = (const aiNode*)vnode;
+	string name( node->mName.data );
+	const aiAnimation* anim  = scene->mAnimations[0];
+	const aiNodeAnim*  anode = nullptr;
+
+	for ( unsigned i = 0 ; i < anim->mNumChannels ; i++ )
+	{
+		anode = anim->mChannels[i];
+		if ( std::string( anode->mNodeName.data ) == name ) break;
+		anode = nullptr;
+	}
+
+	assimp_animated_node_data& a_data = data->nodes[ this_id ];
+
+	a_data.name      = name;
+	a_data.parent_id = parent_id;
+	a_data.bone_id = -1;
+	a_data.transform = nv::assimp_mat4_cast( node->mTransformation );
+
+	if (anode) 
+	{
+		if ( data->pretransformed )
+		{
+			a_data.keys = create_transformed_keys( anode, parent_id >= 0 ? data->nodes[ parent_id ].keys : nullptr );
+		}
+		else
+		{
+			a_data.keys = create_direct_keys( anode );
+		}
+	}
+
+	nv::uint32 next = this_id + 1;
+	for ( unsigned i = 0; i < node->mNumChildren; ++i )
+	{
+		next = load_node( data, node->mChildren[i], next, this_id );
+	}
+	return next;
+}
+
+key_animation_data* nv::assimp_loader::create_transformed_keys( const void* vnode, const key_animation_data* parent_keys )
+{
+	const aiNodeAnim* node = (const aiNodeAnim*)vnode;
+	size_t max_keys = glm::max( node->mNumPositionKeys, node->mNumRotationKeys );
+	nv::transform_vector* keys = new nv::transform_vector;
+	for ( unsigned n = 0; n < max_keys; ++n )
+	{
+		size_t pn = glm::min( node->mNumPositionKeys - 1, n );
+		size_t rn = glm::min( node->mNumRotationKeys - 1, n );
+		nv::vec3 pos = nv::assimp_vec3_cast(node->mPositionKeys[pn].mValue);
+		nv::quat rot = nv::assimp_quat_cast(node->mRotationKeys[rn].mValue);
+		nv::transform ptr;
+		if ( parent_keys )
+		{
+			const nv::transform_vector* pv = (const nv::transform_vector*)parent_keys;
+			if ( pv && pv->size() > 0 ) ptr = pv->get( glm::min( n, pv->size()-1 ) );
+		}
+
+		nv::vec3 rot_pos = ptr.get_orientation() * pos;
+		pos = ptr.get_position() + rot_pos;
+		rot = glm::normalize( ptr.get_orientation() * rot );
+		keys->insert( nv::transform( pos, rot ) );
+	}
+	return keys;
+}
+
+key_animation_data* nv::assimp_loader::create_direct_keys( const void* vnode )
+{
+	const aiNodeAnim* node = (const aiNodeAnim*)vnode;
+	if ( node->mNumPositionKeys == 0 && node->mNumRotationKeys == 0 && node->mNumScalingKeys == 0 ) return nullptr;
+	key_vectors_prs* keys = new key_vectors_prs;
+
+	for ( unsigned np = 0; np < node->mNumPositionKeys; ++np )
+	{
+		keys->insert_position( (float)node->mPositionKeys[np].mTime, assimp_vec3_cast(node->mPositionKeys[np].mValue) );
+	}
+	for ( unsigned np = 0; np < node->mNumRotationKeys; ++np )
+	{
+		keys->insert_rotation( (float)node->mRotationKeys[np].mTime, assimp_quat_cast(node->mRotationKeys[np].mValue) );
+	}
+	if ( node->mNumScalingKeys > 0 )
+	{
+		nv::vec3 scale_vec0 = assimp_vec3_cast( node->mScalingKeys[0].mValue );
+		float scale_value   = glm::length( glm::abs( scale_vec0 - nv::vec3(1,1,1) ) );
+		if ( node->mNumScalingKeys > 1 || scale_value > 0.001 ) 
+		{
+			NV_LOG( nv::LOG_WARNING, "scale key significant!" );
+			for ( unsigned np = 0; np < node->mNumRotationKeys; ++np )
+			{
+				keys->insert_scale( (float)node->mScalingKeys[np].mTime, assimp_vec3_cast(node->mScalingKeys[np].mValue) );
+			}
+		}
+	}
+	return keys;
+}
+
