Index: trunk/src/formats/assimp_loader.cc
===================================================================
--- trunk/src/formats/assimp_loader.cc	(revision 293)
+++ trunk/src/formats/assimp_loader.cc	(revision 294)
@@ -339,5 +339,5 @@
 }
 
-mesh_nodes_data* nv::assimp_loader::release_animation( size_t index )
+mesh_nodes_data* nv::assimp_loader::release_mesh_nodes_data( size_t index /*= 0*/ )
 {
 	if ( m_scene == nullptr ) return nullptr;
@@ -476,5 +476,5 @@
 	}
 
-	mesh_nodes_data* nodes = ( has_bones ? release_merged_bones( meshes ) : release_animation(0) );
+	mesh_nodes_data* nodes = ( has_bones ? release_merged_bones( meshes ) : release_mesh_nodes_data(0) );
 	return new mesh_data_pack( m_mesh_count, meshes, nodes );
 }
@@ -487,7 +487,3 @@
 }
 
-mesh_nodes_data* nv::assimp_loader::release_mesh_nodes_data( size_t index /*= 0 */ )
-{
-	return release_animation( index );
-}
-
+
Index: trunk/src/gfx/keyframed_mesh.cc
===================================================================
--- trunk/src/gfx/keyframed_mesh.cc	(revision 293)
+++ trunk/src/gfx/keyframed_mesh.cc	(revision 294)
@@ -28,5 +28,14 @@
 	m_index_count  = m_mesh_data->get_index_channel()->count;
 	m_vertex_count = m_mesh_data->get_channel<vertex_t>()->count;
-	m_frame_count  = m_mesh_data->get_channel<vertex_pn>()->count / m_vertex_count;
+	m_vchannel     = m_mesh_data->get_channel<vertex_pnt>();
+	m_vsize        = sizeof( vertex_pnt );
+	m_has_tangent  = true;
+	if ( m_vchannel == nullptr )
+	{
+		m_vchannel     = m_mesh_data->get_channel<vertex_pn>();
+		m_has_tangent  = false;
+		m_vsize        = sizeof( vertex_pn );
+	}
+	m_frame_count  = m_vchannel->count / m_vertex_count;
 }
 
@@ -116,6 +125,7 @@
 nv::keyframed_mesh_gpu::keyframed_mesh_gpu( device* a_device, const mesh_data* a_data, const mesh_nodes_data* a_tag_map, program* a_program )
 	: keyframed_mesh( a_device, a_data, a_tag_map )
-	, m_loc_next_position( 0 )
-	, m_loc_next_normal( 0 )
+	, m_loc_next_position( -1 )
+	, m_loc_next_normal( -1 )
+	, m_loc_next_tangent( -1 )
 	, m_gpu_last_frame( 0xFFFFFFFF )
 	, m_gpu_next_frame( 0xFFFFFFFF )
@@ -123,8 +133,11 @@
 	m_loc_next_position = a_program->get_attribute( "nv_next_position" )->get_location();
 	m_loc_next_normal   = a_program->get_attribute( "nv_next_normal" )->get_location();
+	m_loc_next_tangent  = a_program->try_get_attribute_location( "nv_next_tangent" );
 	m_va = a_device->create_vertex_array( a_data, STATIC_DRAW );
 	vertex_buffer* vb = m_va->find_buffer( slot::POSITION );
-	m_va->add_vertex_buffer( m_loc_next_position, vb, FLOAT, 3, 0,              sizeof( vertex_pn ), false );
-	m_va->add_vertex_buffer( m_loc_next_normal,   vb, FLOAT, 3, sizeof( vec3 ), sizeof( vertex_pn ), false );
+	m_va->add_vertex_buffer( m_loc_next_position, vb, FLOAT, 3, 0, m_vsize, false );
+	m_va->add_vertex_buffer( m_loc_next_normal,   vb, FLOAT, 3, sizeof( vec3 ), m_vsize, false );
+	if ( m_has_tangent )
+		m_va->add_vertex_buffer( m_loc_next_tangent, vb, FLOAT, 4, 2*sizeof( vec3 ), m_vsize, false );
 }
 
@@ -136,12 +149,17 @@
 	if ( m_gpu_last_frame != m_last_frame )
 	{
-		m_va->update_vertex_buffer( slot::POSITION, m_last_frame * m_vertex_count * sizeof( vertex_pn ) );
-		m_va->update_vertex_buffer( slot::NORMAL,   m_last_frame * m_vertex_count * sizeof( vertex_pn ) + sizeof( vec3 ) );
+		m_va->update_vertex_buffer( slot::POSITION, m_last_frame * m_vertex_count * m_vsize );
+		m_va->update_vertex_buffer( slot::NORMAL,   m_last_frame * m_vertex_count * m_vsize + sizeof( vec3 ) );
+		if ( m_has_tangent && m_loc_next_tangent != -1 )
+		{
+			m_va->update_vertex_buffer( slot::TANGENT,   m_last_frame * m_vertex_count * m_vsize + 2*sizeof( vec3 ) );
+		}
 		m_gpu_last_frame = m_last_frame;
 	}
 	if ( m_gpu_next_frame != m_next_frame )
 	{
-		m_va->update_vertex_buffer( m_loc_next_position, m_next_frame * m_vertex_count * sizeof( vertex_pn ) );
-		m_va->update_vertex_buffer( m_loc_next_normal,   m_next_frame * m_vertex_count * sizeof( vertex_pn ) + sizeof( vec3 ) );
+		m_va->update_vertex_buffer( m_loc_next_position, m_next_frame * m_vertex_count * m_vsize );
+		m_va->update_vertex_buffer( m_loc_next_normal,   m_next_frame * m_vertex_count * m_vsize + sizeof( vec3 ) );
+		m_va->update_vertex_buffer( m_loc_next_tangent,   m_next_frame * m_vertex_count * m_vsize + 2*sizeof( vec3 ) );
 		m_gpu_next_frame = m_next_frame;
 	}
@@ -151,6 +169,6 @@
 	: keyframed_mesh( a_device, a_data, a_tag_map )
 {
-	m_vb = a_device->create_vertex_buffer( nv::STATIC_DRAW, m_vertex_count * sizeof( vertex_pn ), (void*)m_mesh_data->get_channel<vertex_pn>()->data );
-	m_va->add_vertex_buffers( m_vb, m_mesh_data->get_channel<vertex_pn>() );
+	m_vb = a_device->create_vertex_buffer( nv::STATIC_DRAW, m_vertex_count * m_vsize, (void*)m_vchannel->data );
+	m_va->add_vertex_buffers( m_vb, m_vchannel );
 
 	nv::vertex_buffer* vb = a_device->create_vertex_buffer( nv::STATIC_DRAW, m_vertex_count * sizeof( nv::vec2 ), (void*)m_mesh_data->get_channel<vertex_t>()->data );
@@ -160,5 +178,5 @@
 	m_va->set_index_buffer( ib, m_mesh_data->get_index_channel()->desc.slots[0].etype, true );
 
-	m_vertex.resize( m_vertex_count );
+	m_data = new uint8[ m_vertex_count * m_vsize ];
 }
 
@@ -167,16 +185,39 @@
 	animated_mesh::update( ms );
 
-	const vertex_pn* data = m_mesh_data->get_channel_data<vertex_pn>();
-	const vertex_pn* prev = data + m_vertex_count * m_last_frame;
-	const vertex_pn* next = data + m_vertex_count * m_next_frame;
-
-	for ( size_t i = 0; i < m_vertex_count; ++i )
-	{
-		m_vertex[i].position = glm::mix( prev[i].position, next[i].position, m_interpolation );
-		m_vertex[i].normal   = glm::mix( prev[i].normal,   next[i].normal,   m_interpolation );
+	// TODO: this could be done generic for any data
+	if ( m_has_tangent )
+	{
+		const vertex_pnt* data = m_mesh_data->get_channel_data<vertex_pnt>();
+		const vertex_pnt* prev = data + m_vertex_count * m_last_frame;
+		const vertex_pnt* next = data + m_vertex_count * m_next_frame;
+		      vertex_pnt* vtx  = (vertex_pnt*)m_data;
+		for ( size_t i = 0; i < m_vertex_count; ++i )
+		{
+			vtx[i].position = glm::mix( prev[i].position, next[i].position, m_interpolation );
+			vtx[i].normal   = glm::mix( prev[i].normal,   next[i].normal,   m_interpolation );
+			vtx[i].tangent  = glm::mix( prev[i].tangent,  next[i].tangent,   m_interpolation );
+		}
+	}
+	else
+	{
+		const vertex_pn* data = m_mesh_data->get_channel_data<vertex_pn>();
+		const vertex_pn* prev = data + m_vertex_count * m_last_frame;
+		const vertex_pn* next = data + m_vertex_count * m_next_frame;
+		      vertex_pn* vtx  = (vertex_pn*)m_data;
+
+		for ( size_t i = 0; i < m_vertex_count; ++i )
+		{
+			vtx[i].position = glm::mix( prev[i].position, next[i].position, m_interpolation );
+			vtx[i].normal   = glm::mix( prev[i].normal,   next[i].normal,   m_interpolation );
+		}
 	}
 
 	m_vb->bind();
-	m_vb->update( m_vertex.data(), 0, m_vertex_count * sizeof( vertex_pn ) );
+	m_vb->update( m_data, 0, m_vertex_count * m_vsize );
 	m_vb->unbind();
 }
+
+nv::keyframed_mesh_cpu::~keyframed_mesh_cpu()
+{
+	delete[] m_data;
+}
Index: trunk/src/gfx/mesh_creator.cc
===================================================================
--- trunk/src/gfx/mesh_creator.cc	(revision 293)
+++ trunk/src/gfx/mesh_creator.cc	(revision 294)
@@ -162,2 +162,179 @@
 	}
 }
+
+struct vertex_g
+{
+	nv::vec4 tangent;
+};
+
+void nv::mesh_data_creator::generate_tangents()
+{
+	int p_offset = -1;
+	int n_offset = -1;
+	int t_offset = -1;
+	datatype i_type = NONE;
+	uint32 n_channel_index = 0;
+
+	const mesh_raw_channel* p_channel = nullptr;
+	      mesh_raw_channel* n_channel = nullptr;
+	const mesh_raw_channel* t_channel = nullptr;
+	const mesh_raw_channel* i_channel = nullptr;
+
+	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;
+		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; 
+					p_channel = channel;
+				}
+				break;
+			case slot::NORMAL   : if ( desc.slots[i].etype == FLOAT_VECTOR_3 ) 
+				{
+					n_offset  = desc.slots[i].offset; 
+					n_channel = m_data->m_channels[ c ];
+					n_channel_index = c;
+				}
+				break;
+			case slot::TEXCOORD : if ( desc.slots[i].etype == FLOAT_VECTOR_2 ) 
+				{
+					t_offset  = desc.slots[i].offset; 
+					t_channel = channel;
+				}
+				break;
+			case slot::INDEX    : 
+				{
+					i_type    = desc.slots[i].etype;
+					i_channel = channel;
+				}
+				break;
+			case slot::TANGENT  : return;
+			default             : break;
+		}
+	}
+	if ( !p_channel || !n_channel || !t_channel ) return;
+
+	if ( p_channel->count != n_channel->count || p_channel->count % t_channel->count != 0 || ( i_type != UINT && i_type != USHORT && i_type != NONE ) )
+	{
+		return;
+	}
+
+	mesh_raw_channel* g_channel = mesh_raw_channel::create<vertex_g>( p_channel->count );
+	vec4* tangents              = (vec4*)g_channel->data;
+	vec3* tangents2             = new vec3[ p_channel->count ];
+	uint32 tri_count = i_channel ? i_channel->count / 3 : t_channel->count / 3;
+	uint32 vtx_count = p_channel->count;
+	uint32 sets      = p_channel->count / t_channel->count;
+
+	for ( unsigned int i = 0; i < tri_count; ++i )
+	{
+		uint32 ti0 = 0;
+		uint32 ti1 = 0;
+		uint32 ti2 = 0;
+		if ( i_type == UINT )
+		{
+			const uint32* idata = (const uint32*)i_channel->data;
+			ti0 = idata[ i * 3 ];
+			ti1 = idata[ i * 3 + 1 ];
+			ti2 = idata[ i * 3 + 2 ];
+		}
+		else if ( i_type == USHORT )
+		{
+			const uint16* idata = (const uint16*)i_channel->data;
+			ti0 = idata[ i * 3 ];
+			ti1 = idata[ i * 3 + 1 ];
+			ti2 = idata[ i * 3 + 2 ];
+		}
+		else // if ( i_type == NONE )
+		{
+			ti0 = i * 3;
+			ti1 = i * 3 + 1;
+			ti2 = i * 3 + 2;
+		}
+
+		const vec2& w1 = *((vec2*)(t_channel->data + t_channel->desc.size*ti0 + t_offset ));
+		const vec2& w2 = *((vec2*)(t_channel->data + t_channel->desc.size*ti1 + t_offset ));
+		const vec2& w3 = *((vec2*)(t_channel->data + t_channel->desc.size*ti2 + t_offset ));
+		vec2 st1 = w3 - w1;
+		vec2 st2 = w2 - w1;
+		float stst = (st1.x * st2.y - st2.x * st1.y);
+		float coef = ( stst != 0.0f ? 1.0f / stst : 0.0f );
+
+		for ( uint32 set = 0; set < sets; ++set )
+		{
+			uint32 nti0 = t_channel->count * set + ti0;
+			uint32 nti1 = t_channel->count * set + ti1;
+			uint32 nti2 = t_channel->count * set + ti2;
+			vec3 v1 = *((vec3*)(p_channel->data + p_channel->desc.size*nti0 + p_offset ));
+			vec3 v2 = *((vec3*)(p_channel->data + p_channel->desc.size*nti1 + p_offset ));
+			vec3 v3 = *((vec3*)(p_channel->data + p_channel->desc.size*nti2 + p_offset ));
+			vec3 xyz1 = v3 - v1;
+			vec3 xyz2 = v2 - v1;
+
+			//glm::vec3 normal = glm::cross( xyz1, xyz2 );
+			//
+			//vtcs[ ti0 ].normal += normal;
+			//vtcs[ ti1 ].normal += normal;
+			//vtcs[ ti2 ].normal += normal;
+			vec3 tangent  = (( xyz1 * st2.y ) - ( xyz2 * st1.y )) * coef;
+			vec3 tangent2 = (( xyz2 * st1.x ) - ( xyz1 * st2.x )) * coef;
+
+			tangents[nti0] = vec4( vec3( tangents[nti0] ) + tangent, 0 );
+			tangents[nti1] = vec4( vec3( tangents[nti1] ) + tangent, 0 );
+			tangents[nti2] = vec4( vec3( tangents[nti2] ) + tangent, 0 );
+
+			tangents2[nti0] += tangent2;
+			tangents2[nti1] += tangent2;
+			tangents2[nti2] += tangent2;
+		}
+	}
+
+	for ( unsigned int i = 0; i < vtx_count; ++i )
+	{
+		const vec3 n = *((vec3*)(n_channel->data + n_channel->desc.size*i + n_offset ));
+		const vec3 t = vec3(tangents[i]);
+		if ( ! ( t.x == 0.0f && t.y == 0.0f && t.z == 0.0f ) )
+		{
+			tangents[i]    = vec4( glm::normalize(t - n * glm::dot( n, t )), 0.0f ); 
+			tangents[i][3] = (glm::dot(glm::cross(n, t), tangents2[i]) < 0.0f) ? -1.0f : 1.0f;
+		}
+	}
+	delete tangents2;
+
+	m_data->m_channels[ n_channel_index ] = merge_channels( n_channel, g_channel );
+	delete n_channel;
+	delete g_channel;
+}
+
+nv::mesh_raw_channel* nv::mesh_data_creator::merge_channels( mesh_raw_channel* a, mesh_raw_channel* b )
+{
+	NV_ASSERT( a->count == b->count, "merge_channel - bad channels!" );
+	vertex_descriptor adesc = a->desc;
+	vertex_descriptor bdesc = b->desc;
+	uint32            count = a->count;
+
+	vertex_descriptor desc  = a->desc;
+	for ( uint32 i = 0; i < bdesc.count; i++ )
+	{
+		desc.slots[desc.count+i] = bdesc.slots[i];
+		desc.slots[desc.count+i].offset += desc.size;
+	}
+	desc.size  += bdesc.size;
+	desc.count += bdesc.count;
+	uint8* data = new uint8[ count * desc.size ];
+	for ( uint32 i = 0; i < count; ++i )
+	{
+		std::copy_n( a->data + i * adesc.size, adesc.size, data + i*desc.size );
+		std::copy_n( b->data + i * bdesc.size, bdesc.size, data + i*desc.size + adesc.size );
+	}
+	mesh_raw_channel* result = new mesh_raw_channel;
+	result->count = count;
+	result->desc  = desc;
+	result->data  = data;
+	return result;
+}
