Index: /trunk/nv/formats/assimp_loader.hh
===================================================================
--- /trunk/nv/formats/assimp_loader.hh	(revision 292)
+++ /trunk/nv/formats/assimp_loader.hh	(revision 293)
@@ -20,6 +20,5 @@
 	{
 	public:
-		assimp_loader( const string& a_ext, const mat3& a_rotate_transform, float a_scale, bool pre_transform, uint32 a_assimp_flags = 0 );
-		assimp_loader( const string& a_ext, bool pre_transform, uint32 a_assimp_flags = 0 );
+		explicit assimp_loader( const string& a_ext, uint32 a_assimp_flags = 0 );
 		virtual bool load( stream& source );
 		virtual mesh_data* release_mesh_data( size_t index = 0 );
@@ -35,55 +34,13 @@
 		bool load_bones( size_t index, std::vector< mesh_node_data >& bones );
 		void load_mesh_data( mesh_data* data, size_t index );
-		void initialize( const string& a_ext, const mat3& a_rotate_transform, float a_scale, uint32 a_assimp_flags );
-		sint16 load_node( uint32 anim_id, mesh_node_data* nodes, const void* vnode, sint16 this_id, sint16 parent_id, uint32& max_frames );
+		sint16 load_node( uint32 anim_id, mesh_node_data* nodes, const void* vnode, sint16 this_id, sint16 parent_id );
 		uint32 count_nodes( const void* node ) const;
-		void create_transformed_keys( mesh_node_data* data, const void* vnode, const mesh_node_data* parent );
-		void create_direct_keys( mesh_node_data* data, const void* vnode );
+		void create_keys( mesh_node_data* data, const void* vnode );
 
 		string_table_creator m_strings;
 		string m_ext;
-		mat3   m_r33;
-		mat3   m_ri33;
-		float  m_scale;
 		uint32 m_assimp_flags;
 		const void* m_scene;
 		size_t m_mesh_count;
-		bool   m_flat;
-	};
-
-	struct assimp_plain_vtx 
-	{
-		vec3 position;
-		vec3 normal;
-		vec2 texcoord;
-		vec4 tangent;
-
-		assimp_plain_vtx() {}
-		assimp_plain_vtx( const vec3& v, const vec2& t, const vec3& n, const vec4& g )
-		{
-			position = v;
-			texcoord = t;
-			normal   = n;
-			tangent  = g;
-		}
-	};
-
-	struct assimp_skinned_vtx 
-	{
-		vec3  position;
-		vec3  normal;
-		vec2  texcoord;
-		vec4  tangent;
-		ivec4 boneindex;
-		vec4  boneweight;
-
-		assimp_skinned_vtx() {}
-		assimp_skinned_vtx( const vec3& v, const vec2& t, const vec3& n, const vec4& g )
-		{
-			position = v;
-			texcoord = t;
-			normal   = n;
-			tangent  = g;
-		}
 	};
 
Index: /trunk/nv/gfx/animation.hh
===================================================================
--- /trunk/nv/gfx/animation.hh	(revision 292)
+++ /trunk/nv/gfx/animation.hh	(revision 293)
@@ -135,10 +135,10 @@
 				if ( ksi.vslot != animation_slot::TIME )
 				{
-					uint32 index = final_key.count;
-					final_key.slots[ index ].offset = final_key.size;
-					final_key.slots[ index ].etype  = ksi.etype;
-					final_key.slots[ index ].vslot  = ksi.vslot;
-					final_key.size += get_datatype_info( ksi.etype ).size;
-					final_key.count++;
+					uint32 index = m_final_key.count;
+					m_final_key.slots[ index ].offset = m_final_key.size;
+					m_final_key.slots[ index ].etype  = ksi.etype;
+					m_final_key.slots[ index ].vslot  = ksi.vslot;
+					m_final_key.size += get_datatype_info( ksi.etype ).size;
+					m_final_key.count++;
 				}
 			}
@@ -153,5 +153,5 @@
 				pkey += m_channels[i]->get_raw( index, pkey );
 			}
-			return extract_matrix_raw( final_key, key );
+			return extract_matrix_raw( m_final_key, key );
 		};
 
@@ -164,5 +164,5 @@
 				pkey += m_channels[i]->get_raw( index, pkey );
 			}
-			return extract_transform_raw( final_key, key );
+			return extract_transform_raw( m_final_key, key );
 		};
 
@@ -175,5 +175,5 @@
 				pkey += m_channels[i]->interpolate_raw( time, pkey );
 			}
-			return extract_matrix_raw( final_key, key );
+			return extract_matrix_raw( m_final_key, key );
 		};
 
@@ -186,9 +186,10 @@
 				pkey += m_channels[i]->interpolate_raw( time, pkey );
 			}
-			return extract_transform_raw( final_key, key );
+			return extract_transform_raw( m_final_key, key );
 		};
 
 		size_t get_channel_count() const { return m_channels.size(); }
 		const key_raw_channel* get_channel( size_t index ) const { return m_channels[ index ]; }
+		const key_descriptor& get_final_key() const { return m_final_key; }
 
 		virtual ~key_data()
@@ -197,5 +198,5 @@
 		}
 	private:
-		key_descriptor final_key;
+		key_descriptor m_final_key;
 		std::vector< key_raw_channel* > m_channels;
 	};
Index: /trunk/nv/gfx/mesh_creator.hh
===================================================================
--- /trunk/nv/gfx/mesh_creator.hh	(revision 293)
+++ /trunk/nv/gfx/mesh_creator.hh	(revision 293)
@@ -0,0 +1,60 @@
+// 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
+
+#ifndef NV_MESH_CREATOR_HH
+#define NV_MESH_CREATOR_HH
+
+#include <nv/common.hh>
+#include <nv/math.hh>
+#include <nv/interface/mesh_data.hh>
+
+namespace nv
+{
+
+	class mesh_data_creator
+	{
+	public:
+		mesh_data_creator( mesh_data* data ) : m_data( data ) {}
+		// assumes that position and normal is vec3, tangent is vec4
+		void transform( float scale, const mat3& r33 );
+	private:
+		mesh_data* m_data;
+	};
+
+	class mesh_nodes_creator
+	{
+	public:
+		mesh_nodes_creator( mesh_nodes_data* data ) : m_data( data ) {}
+		// 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 );
+	private:
+		mesh_nodes_data* m_data;
+	};
+
+	class mesh_creator
+	{
+	public:
+		mesh_creator( mesh_data_pack* pack ) : m_pack( pack ) {}
+		// assumes that keys are equally spaced
+		void pre_transform_keys() { mesh_nodes_creator( m_pack->m_nodes ).pre_transform_keys(); }
+		// assumes that keys are equally spaced
+		void merge_keys() { mesh_nodes_creator( m_pack->m_nodes ).merge_keys(); }
+		// assumes that position and normal is vec3, tangent is vec4
+		void transform( float scale, const mat3& r33 ) 
+		{
+			for ( size_t m = 0; m < m_pack->m_count; ++m )
+				mesh_data_creator( &(m_pack->m_meshes[m]) ).transform( scale, r33 );
+			mesh_nodes_creator( m_pack->m_nodes ).transform( scale, r33 );
+		}
+	private:
+		mesh_data_pack* m_pack;
+	};
+}
+
+#endif // NV_MESH_CREATOR_HH
Index: /trunk/nv/interface/interpolation_raw.hh
===================================================================
--- /trunk/nv/interface/interpolation_raw.hh	(revision 292)
+++ /trunk/nv/interface/interpolation_raw.hh	(revision 293)
@@ -137,4 +137,45 @@
 	}
 
+	inline void transform_key_position( uint8* data, float scale, const mat3& r33 )
+	{
+		vec3& p = *((vec3*)(data));
+		p = r33 * p * scale;
+	}
+	inline void transform_key_rotation( uint8* data, const mat3& r33, const mat3& ri33 )
+	{
+		quat& r = *((quat*)(data));
+		r = glm::quat_cast( r33 * glm::mat3_cast( r ) * ri33 );
+	}
+	inline void transform_key_scale( void* data, float scale )
+	{
+		vec3& s = *((vec3*)(data));
+		s = s * scale;
+	}
+	inline void transform_key_transform( uint8* data, float scale, const mat3& r33, const mat3& ri33 )
+	{
+		transform& t = *((transform*)(data));
+		t = transform( 
+			r33 * t.get_position() * scale,
+			glm::quat_cast( r33 * glm::mat3_cast( t.get_orientation() ) * ri33 )
+		);
+	}
+
+	inline void transform_key_raw( const key_descriptor& desc, uint8* data, float scale, const mat3& r33, const mat3& ri33 )
+	{
+		for ( uint32 i = 0; i < desc.count; ++i )
+		{
+			uint32 offset = desc.slots[i].offset;
+			switch ( desc.slots[i].vslot )
+			{
+			case animation_slot::TIME:     break;
+			case animation_slot::POSITION: transform_key_position( data + offset, scale, r33 ); break;
+			case animation_slot::ROTATION: transform_key_rotation( data + offset, r33, ri33 ); break;
+			case animation_slot::SCALE:    transform_key_scale( data + offset, scale ); break;
+			case animation_slot::TFORM:    transform_key_transform( data + offset, scale, r33, ri33 ); break;
+			default:
+				break;
+			}
+		}
+	}
 
 }
Index: /trunk/nv/interface/mesh_data.hh
===================================================================
--- /trunk/nv/interface/mesh_data.hh	(revision 292)
+++ /trunk/nv/interface/mesh_data.hh	(revision 293)
@@ -1,3 +1,3 @@
-// Copyright (C) 2012-2013 ChaosForge / Kornel Kisielewicz
+// Copyright (C) 2012-2014 ChaosForge Ltd
 // http://chaosforge.org/
 //
@@ -9,6 +9,8 @@
 
 #include <nv/common.hh>
-#include <vector>
 #include <nv/math.hh>
+#include <nv/string.hh>
+#include <nv/array.hh>
+#include <nv/gfx/animation.hh>
 #include <nv/interface/vertex.hh>
 
@@ -22,4 +24,6 @@
 	struct mesh_raw_channel
 	{
+		friend class mesh_creator;
+
 		vertex_descriptor desc;
 		uint8*            data;
@@ -78,4 +82,5 @@
 	class mesh_data
 	{
+		friend class mesh_creator;
 	public:
 		explicit mesh_data() : m_index_channel( nullptr ) {}
@@ -95,4 +100,10 @@
 		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_channel_count() const { return m_channels.size(); }
+		const mesh_raw_channel* get_channel( size_t index ) const 
+		{ 
+			if ( m_channels.size() > index ) return m_channels[index];
+			return nullptr;
+		}
 
 		size_t get_count() const 
@@ -164,4 +175,93 @@
 	};
 
+	struct mesh_node_data
+	{
+		string    name;
+		sint16    target_id;
+		sint16    parent_id;
+		mat4      transform;
+		key_data* data;
+	};
+
+	class mesh_nodes_data
+	{
+		friend class mesh_creator;
+		friend class mesh_nodes_creator;
+	public:
+		explicit mesh_nodes_data( const string& name, uint32 count, mesh_node_data* nodes )
+			: m_name( name ), m_count( count ), m_nodes( nodes ), m_frame_rate(0), m_duration(0.0f), m_flat( false )
+		{
+		}
+
+		explicit mesh_nodes_data( const string& name, uint32 count, mesh_node_data* nodes,
+			uint16 a_fps, float a_frames, bool a_flat )
+			: m_name( name ), m_count( count ), m_nodes( nodes ), m_frame_rate(a_fps), m_duration(a_frames), m_flat( a_flat )
+		{
+		}
+
+		size_t get_count() const { return m_count; }
+
+		const mesh_node_data* get_node( size_t i ) const 
+		{
+			if ( i >= m_count ) return nullptr;
+			return &m_nodes[i];
+		}
+
+		key_data* release_node_data( size_t i )
+		{
+			key_data* result = m_nodes[i].data;
+			m_nodes[i].data = nullptr;
+			return result;
+		}
+
+		bool is_flat() const { return m_flat; }
+		uint16 get_frame_rate() const { return m_frame_rate; }
+		float get_duration() const { return m_duration; }
+		void set_name( const std::string& name ) { m_name = name; }
+		const string& get_name() const { return m_name; }
+
+		~mesh_nodes_data()
+		{
+			if ( m_count > 0 )
+				for ( uint32 i = 0; i < m_count; ++i ) delete m_nodes[i].data;
+			delete[] m_nodes;
+		}
+
+	private:
+		string m_name;
+		uint32 m_count;
+		mesh_node_data* m_nodes;
+		uint16  m_frame_rate;
+		float   m_duration;
+		bool    m_flat;
+	};
+
+	class mesh_data_pack
+	{
+		friend class mesh_creator;
+	public:
+		explicit mesh_data_pack( uint32 a_count, mesh_data* a_meshes, mesh_nodes_data* a_nodes = nullptr )
+		{
+			m_count  = a_count;
+			m_meshes = a_meshes;
+			m_nodes  = a_nodes;
+		}
+		const mesh_data* get_mesh( uint32 index ) const
+		{
+			if ( index >= m_count ) return nullptr;
+			return &m_meshes[ index ];
+		}
+		uint32 get_count() const { return m_count; }
+		const mesh_nodes_data* get_nodes() const { return m_nodes; }
+		~mesh_data_pack()
+		{
+			delete[] m_meshes;
+			delete   m_nodes;
+		}
+	private:
+		uint32           m_count;
+		mesh_data*       m_meshes;
+		mesh_nodes_data* m_nodes;
+	};
 }
 
Index: /trunk/nv/interface/mesh_loader.hh
===================================================================
--- /trunk/nv/interface/mesh_loader.hh	(revision 292)
+++ /trunk/nv/interface/mesh_loader.hh	(revision 293)
@@ -1,3 +1,3 @@
-// Copyright (C) 2012-2013 ChaosForge / Kornel Kisielewicz
+// Copyright (C) 2012-2014 ChaosForge Ltd
 // http://chaosforge.org/
 //
@@ -26,92 +26,4 @@
 {
 
-	struct mesh_node_data
-	{
-		string    name;
-		sint16    target_id;
-		sint16    parent_id;
-		mat4      transform;
-		key_data* data;
-	};
-
-	class mesh_nodes_data
-	{
-	public:
-		explicit mesh_nodes_data( const string& name, uint32 count, mesh_node_data* nodes )
-			: m_name( name ), m_count( count ), m_nodes( nodes ), m_frame_rate(0), m_duration(0.0f), m_flat( false )
-		{
-		}
-
-		explicit mesh_nodes_data( const string& name, uint32 count, mesh_node_data* nodes,
-			uint16 a_fps, float a_frames, bool a_flat )
-			: m_name( name ), m_count( count ), m_nodes( nodes ), m_frame_rate(a_fps), m_duration(a_frames), m_flat( a_flat )
-		{
-		}
-
-		size_t get_count() const { return m_count; }
-
-		const mesh_node_data* get_node( size_t i ) const 
-		{
-			if ( i >= m_count ) return nullptr;
-			return &m_nodes[i];
-		}
-
-		key_data* release_node_data( size_t i )
-		{
-			key_data* result = m_nodes[i].data;
-			m_nodes[i].data = nullptr;
-			return result;
-		}
-
-		bool is_flat() const { return m_flat; }
-		uint16 get_frame_rate() const { return m_frame_rate; }
-		float get_duration() const { return m_duration; }
-		void set_name( const std::string& name ) { m_name = name; }
-		const string& get_name() const { return m_name; }
-
-		~mesh_nodes_data()
-		{
-			if ( m_count > 0 )
-				for ( uint32 i = 0; i < m_count; ++i ) delete m_nodes[i].data;
-			delete[] m_nodes;
-		}
-
-	private:
-		string m_name;
-		uint32 m_count;
-		mesh_node_data* m_nodes;
-		uint16  m_frame_rate;
-		float   m_duration;
-		bool    m_flat;
-	};
-
-	class mesh_data_pack
-	{
-	public:
-		explicit mesh_data_pack( uint32 a_count, mesh_data* a_meshes, mesh_nodes_data* a_nodes = nullptr )
-		{
-			m_count  = a_count;
-			m_meshes = a_meshes;
-			m_nodes  = a_nodes;
-		}
-		const mesh_data* get_mesh( uint32 index ) const
-		{
-			if ( index >= m_count ) return nullptr;
-			return &m_meshes[ index ];
-		}
-		uint32 get_count() const { return m_count; }
-		const mesh_nodes_data* get_nodes() const { return m_nodes; }
-		~mesh_data_pack()
-		{
-			delete[] m_meshes;
-			delete   m_nodes;
-		}
-	private:
-		uint32           m_count;
-		mesh_data*       m_meshes;
-		mesh_nodes_data* m_nodes;
-	};
-
-
 	class mesh_loader
 	{
Index: /trunk/src/formats/assimp_loader.cc
===================================================================
--- /trunk/src/formats/assimp_loader.cc	(revision 292)
+++ /trunk/src/formats/assimp_loader.cc	(revision 293)
@@ -9,4 +9,5 @@
 #include <glm/gtx/transform.hpp>
 #include "nv/io/std_stream.hh"
+#include "nv/gfx/mesh_creator.hh"
 #include "nv/lib/assimp.hh"
 
@@ -14,4 +15,40 @@
 
 const int MAX_BONES = 64;
+
+struct assimp_plain_vtx 
+{
+	vec3 position;
+	vec3 normal;
+	vec2 texcoord;
+	vec4 tangent;
+
+	assimp_plain_vtx() {}
+	assimp_plain_vtx( const vec3& v, const vec2& t, const vec3& n, const vec4& g )
+	{
+		position = v;
+		texcoord = t;
+		normal   = n;
+		tangent  = g;
+	}
+};
+
+struct assimp_skinned_vtx 
+{
+	vec3  position;
+	vec3  normal;
+	vec2  texcoord;
+	vec4  tangent;
+	ivec4 boneindex;
+	vec4  boneweight;
+
+	assimp_skinned_vtx() {}
+	assimp_skinned_vtx( const vec3& v, const vec2& t, const vec3& n, const vec4& g )
+	{
+		position = v;
+		texcoord = t;
+		normal   = n;
+		tangent  = g;
+	}
+};
 
 struct assimp_key_p  { float time; vec3 position; };
@@ -21,23 +58,8 @@
 
 
-nv::assimp_loader::assimp_loader( const string& a_ext, const mat3& a_rotate_transform, float a_scale, bool pre_transform, uint32 a_assimp_flags /*= 0 */ ) 
-	: m_scene( nullptr ), m_flat( pre_transform ), m_mesh_count(0)
-{
-	initialize( a_ext, a_rotate_transform, a_scale, a_assimp_flags );
-}
-
-nv::assimp_loader::assimp_loader( const string& a_ext, bool pre_transform, uint32 a_assimp_flags /*= 0 */ ) 
-	: m_scene( nullptr ), m_flat( pre_transform ), m_mesh_count(0)
-{
-	initialize( a_ext, mat3(), 1.0f, a_assimp_flags );
-}
-
-
-void nv::assimp_loader::initialize( const string& a_ext, const mat3& a_rotate_transform, float a_scale, uint32 a_assimp_flags )
+nv::assimp_loader::assimp_loader( const string& a_ext, uint32 a_assimp_flags /*= 0 */ ) 
+	: m_scene( nullptr ), m_mesh_count(0)
 {
 	m_ext   = a_ext;
-	m_r33   = a_rotate_transform;
-	m_ri33  = glm::transpose( m_r33 );
-	m_scale = a_scale;
 	m_assimp_flags = a_assimp_flags;
 	if ( m_assimp_flags == 0 )
@@ -97,8 +119,4 @@
 	data->set_name( mesh->mName.data );
 
-	vec3 vertex_offset     = glm::vec3(); 
-	mat3 vertex_transform  = m_scale * m_r33;
-	mat3 normal_transform  = m_r33;
-
 	bool skinned = mesh->mNumBones > 0;
 	mesh_raw_channel* channel = nullptr;
@@ -111,8 +129,8 @@
 	for (unsigned int i=0; i<mesh->mNumVertices; i++)
 	{
-		vec3 v = vertex_transform * assimp_vec3_cast( mesh->mVertices[ i ] ) + vertex_offset;
-		vec3 n = glm::normalize( normal_transform * assimp_vec3_cast( mesh->mNormals[ i ] ) );
-		vec3 t = glm::normalize( normal_transform * assimp_vec3_cast( mesh->mTangents[ i ] ) );
-		vec3 b = glm::normalize( normal_transform * assimp_vec3_cast( mesh->mBitangents[ i ] ) );
+		vec3 v = assimp_vec3_cast( mesh->mVertices[ i ] );
+		vec3 n = glm::normalize( assimp_vec3_cast( mesh->mNormals[ i ] ) );
+		vec3 t = glm::normalize( assimp_vec3_cast( mesh->mTangents[ i ] ) );
+		vec3 b = glm::normalize( assimp_vec3_cast( mesh->mBitangents[ i ] ) );
 		vec2 s = assimp_st_cast( mesh->mTextureCoords[ 0 ][ i ] );
 
@@ -176,11 +194,8 @@
 	const aiMesh*  mesh  = scene->mMeshes[ index ];
 
-	mat4 bone_transform     = mat4( ( 1.f/m_scale * m_ri33 ) ); 
-	mat4 bone_pre_transform = mat4( m_scale * m_r33 );
-
 	for (unsigned int m=0; m<mesh->mNumBones; m++)
 	{
 		aiBone* bone   = mesh->mBones[m];
-		mat4    offset = bone_pre_transform * assimp_mat4_cast( bone->mOffsetMatrix ) * bone_transform;
+		mat4    offset = assimp_mat4_cast( bone->mOffsetMatrix );
 		bones[m].name = bone->mName.data;
 		bones[m].data = nullptr;
@@ -338,17 +353,9 @@
 	uint16 frame_rate     = (uint16)anim->mTicksPerSecond;
 	float  duration       = (float)anim->mDuration;
-	bool   flat           = m_flat;
-	uint32 max_frames     = 0;
-
-	load_node( index, data, root, 0, -1, max_frames );
-
-	// DAE pre_transform hack
-	if ( m_flat && frame_rate == 1 )
-	{
-		frame_rate = 32;
-		duration   = (float)max_frames;
-	}
-
-	return new mesh_nodes_data( anim->mName.data, count, data, frame_rate, duration, flat ) ;
+	bool   flat           = false;
+
+	load_node( index, data, root, 0, -1 );
+
+	return new mesh_nodes_data( anim->mName.data, count, data, frame_rate, duration, flat );
 }
 
@@ -364,5 +371,5 @@
 }
 
-nv::sint16 nv::assimp_loader::load_node( uint32 anim_id, mesh_node_data* nodes, const void* vnode, sint16 this_id, sint16 parent_id, uint32& max_frames )
+nv::sint16 nv::assimp_loader::load_node( uint32 anim_id, mesh_node_data* nodes, const void* vnode, sint16 this_id, sint16 parent_id )
 {
 	const aiScene* scene = (const aiScene*)m_scene;
@@ -393,21 +400,10 @@
 	a_data.data = nullptr;
 
-	if (anode) 
-	{
-		if ( m_flat )
-		{
-			create_transformed_keys( &a_data, anode, parent_id >= 0 ? &(nodes[ parent_id ]) : nullptr );
-		}
-		else
-		{
-			create_direct_keys( &a_data, anode );
-		}
-		max_frames = glm::max<uint32>( a_data.data->get_channel(0)->count, max_frames );
-	}
+	if (anode) create_keys( &a_data, anode );
 
 	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, max_frames );
+		next = load_node( anim_id, nodes, node->mChildren[i], next, this_id );
 	}
 
@@ -415,37 +411,5 @@
 }
 
-void nv::assimp_loader::create_transformed_keys( mesh_node_data* data, const void* vnode, const mesh_node_data* parent )
-{
-	const aiNodeAnim* node = (const aiNodeAnim*)vnode;
-	size_t max_keys = glm::max( node->mNumPositionKeys, node->mNumRotationKeys );
-
-	key_raw_channel* raw_channel = key_raw_channel::create<assimp_key_tr>( max_keys );
-	data->data = new key_data;
-	data->data->add_channel( raw_channel );
-	assimp_key_tr* channel = ((assimp_key_tr*)(raw_channel->data));
-
-	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 = m_r33 * nv::assimp_vec3_cast(node->mPositionKeys[pn].mValue) * m_scale;
-		nv::quat rot = glm::quat_cast( m_r33 * glm::mat3_cast( assimp_quat_cast(node->mRotationKeys[rn].mValue ) ) * m_ri33 );
-		// TODO: only do the calculation when a rotate transform is present!
-		nv::transform ptr;
-		if ( parent && parent->data )
-		{
-			const key_raw_channel* pchannel = parent->data->get_channel(0);
-			if ( pchannel && pchannel->count > 0 )
-			{
-				ptr = ((assimp_key_tr*)pchannel->data)[ glm::min( n, pchannel->count-1 ) ].tform;
-			}
-		}
-
-		nv::transform key( ptr * nv::transform( pos, rot ) );
-		channel[n].tform = key;
-	}
-}
-
-void nv::assimp_loader::create_direct_keys( mesh_node_data* data, const void* vnode )
+void nv::assimp_loader::create_keys( mesh_node_data* data, const void* vnode )
 {
 	const aiNodeAnim* node = (const aiNodeAnim*)vnode;
@@ -469,10 +433,10 @@
 	{
 		pchannel[np].time     = (float)node->mPositionKeys[np].mTime;
-		pchannel[np].position = m_r33 * assimp_vec3_cast(node->mPositionKeys[np].mValue) * m_scale;
+		pchannel[np].position = assimp_vec3_cast(node->mPositionKeys[np].mValue);
 	}
 	for ( unsigned np = 0; np < node->mNumRotationKeys; ++np )
 	{
 		rchannel[np].time     = (float)node->mRotationKeys[np].mTime;
-		rchannel[np].rotation = glm::quat_cast( m_r33 * glm::mat3_cast( assimp_quat_cast(node->mRotationKeys[np].mValue ) ) * m_ri33 );
+		rchannel[np].rotation = assimp_quat_cast(node->mRotationKeys[np].mValue );
 	}
 // 	if ( node->mNumScalingKeys > 0 )
Index: /trunk/src/gfx/mesh_creator.cc
===================================================================
--- /trunk/src/gfx/mesh_creator.cc	(revision 293)
+++ /trunk/src/gfx/mesh_creator.cc	(revision 293)
@@ -0,0 +1,163 @@
+// Copyright (C) 2012-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/gfx/mesh_creator.hh"
+
+struct nv_key_transform { nv::transform tform; };
+
+void nv::mesh_nodes_creator::pre_transform_keys()
+{
+	if ( m_data->m_flat ) return;
+	merge_keys();
+	uint32 max_frames = 0;
+	for ( size_t i = 0; i < m_data->get_count(); ++i )
+	{
+		sint16 parent_id = m_data->m_nodes[i].parent_id;
+		key_data* keys   = m_data->m_nodes[i].data;
+		key_data* pkeys  = ( parent_id != -1 ? m_data->m_nodes[parent_id].data : nullptr );
+		size_t count     = ( keys ? keys->get_channel(0)->count : 0 );
+		size_t pcount    = ( pkeys ? pkeys->get_channel(0)->count : 0 );
+		max_frames = glm::max<uint32>( count, max_frames );
+		if ( pkeys && pkeys->get_channel_count() > 0 && keys && keys->get_channel_count() > 0 )
+		{
+			nv_key_transform*  channel = ((nv_key_transform*)(keys->get_channel(0)->data));
+			nv_key_transform* pchannel = ((nv_key_transform*)(pkeys->get_channel(0)->data));
+			for ( unsigned n = 0; n < count; ++n )
+			{
+				channel[n].tform = pchannel[ glm::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_duration   = (float)max_frames;
+	}
+
+	m_data->m_flat = true;
+}
+
+// TODO: DELETE
+struct assimp_key_p  { float time; nv::vec3 position; };
+struct assimp_key_r  { float time; nv::quat rotation; };
+
+
+void nv::mesh_nodes_creator::merge_keys()
+{
+	for ( size_t i = 0; i < m_data->get_count(); ++i )
+	{
+		key_data* old_keys = m_data->m_nodes[i].data;
+		if ( old_keys && old_keys->get_channel_count() > 0 )
+		{
+			size_t chan_count = old_keys->get_channel_count();
+			if ( chan_count == 1 
+				&& old_keys->get_channel(0)->desc.count == 1 
+				&& old_keys->get_channel(0)->desc.slots[0].etype == TRANSFORM ) continue;
+
+			size_t max_keys = 0;
+			for ( size_t c = 0; c < chan_count; ++c )
+			{
+				max_keys = glm::max( max_keys, old_keys->get_channel(c)->count );
+			}
+
+			key_raw_channel* raw_channel = key_raw_channel::create<nv_key_transform>( max_keys );
+			key_data* new_keys = new key_data;
+			new_keys->add_channel( raw_channel );
+			nv_key_transform* channel = ((nv_key_transform*)(raw_channel->data));
+			key_descriptor final_key = old_keys->get_final_key();
+
+			for ( unsigned n = 0; n < max_keys; ++n )
+			{
+				float key[ 16 ];
+				float* pkey = key;
+
+				for ( uint16 c = 0; c < chan_count; ++c )
+				{
+					size_t idx = glm::min( old_keys->get_channel(c)->count - 1, n );
+					pkey += old_keys->get_channel(c)->get_raw( idx, pkey );
+				}
+				channel[n].tform = extract_transform_raw( final_key, key );
+			}
+
+			delete old_keys;
+			m_data->m_nodes[i].data = new_keys;
+		}
+	}
+}
+
+void nv::mesh_nodes_creator::transform( float scale, const mat3& r33 )
+{
+	mat3 ri33 = glm::inverse( r33 );
+	mat4 pre_transform ( scale * r33 );
+	mat4 post_transform( 1.f/scale * ri33 ); 
+
+	for ( size_t i = 0; i < m_data->get_count(); ++i )
+	{
+		mesh_node_data& node = m_data->m_nodes[i];
+		node.transform = pre_transform * node.transform * post_transform;
+		if ( node.data )
+		{
+			key_data* kdata  = node.data;
+			for ( size_t c = 0; c < kdata->get_channel_count(); ++c )
+			{
+				const key_raw_channel* channel = kdata->get_channel(c);
+				size_t key_size = channel->desc.size;
+				for ( size_t n = 0; n < channel->count; ++n )
+				{
+					transform_key_raw( channel->desc, (uint8*)(channel->data + n * key_size), scale, r33, ri33 );
+				}
+			}
+		}
+	}
+}
+
+void nv::mesh_data_creator::transform( float scale, const mat3& r33 )
+{
+	vec3 vertex_offset     = glm::vec3(); 
+	mat3 vertex_transform  = scale * r33;
+	mat3 normal_transform  = r33;
+
+	for ( uint32 c = 0; c < m_data->get_channel_count(); ++c )
+	{
+		const mesh_raw_channel* channel = m_data->get_channel(c);
+		const vertex_descriptor& desc   = channel->desc;
+		uint8* raw_data = channel->data;
+		int vtx_size = desc.size;
+		int p_offset = -1;
+		int n_offset = -1;
+		int t_offset = -1;
+		for ( uint32 i = 0; i < desc.count; ++i )
+			switch ( desc.slots[i].vslot )
+			{
+				case slot::POSITION : if ( desc.slots[i].etype == FLOAT_VECTOR_3 ) p_offset = desc.slots[i].offset; break;
+				case slot::NORMAL   : if ( desc.slots[i].etype == FLOAT_VECTOR_3 ) n_offset = desc.slots[i].offset; break;
+				case slot::TANGENT  : if ( desc.slots[i].etype == FLOAT_VECTOR_4 ) t_offset = desc.slots[i].offset; break;
+				default             : break;
+			}
+
+		if ( p_offset != -1 )
+			for ( uint32 i = 0; i < channel->count; i++)
+			{
+				vec3& p = *((vec3*)(raw_data + vtx_size*i + p_offset ));
+				p = vertex_transform * p + vertex_offset;
+			}
+
+		if ( n_offset != -1 )
+			for ( uint32 i = 0; i < channel->count; i++)
+			{
+				vec3& n = *((vec3*)(raw_data + vtx_size*i + n_offset ));
+				n = glm::normalize( normal_transform * n );
+			}
+		if ( t_offset != -1 )
+			for ( uint32 i = 0; i < channel->count; i++)
+			{
+				vec4& t = *((vec4*)(raw_data + vtx_size*i + t_offset ));
+				t = vec4( glm::normalize( normal_transform * vec3(t) ), t[3] );
+			}
+	}
+}
