Index: trunk/src/core/ascii_printer.cc
===================================================================
--- trunk/src/core/ascii_printer.cc	(revision 486)
+++ trunk/src/core/ascii_printer.cc	(revision 486)
@@ -0,0 +1,52 @@
+// Copyright (C) 2015-2015 ChaosForge Ltd
+// http://chaosforge.org/
+//
+// This file is part of Nova libraries. 
+// For conditions of distribution and use, see copying.txt file in root folder.
+
+#include "nv/core/ascii_printer.hh"
+
+using namespace nv;
+
+ascii_printer::ascii_printer( terminal * term )
+	: m_terminal( term )
+{
+	
+}
+
+void ascii_printer::print( const string_view& text, const position& p, uint32 color )
+{
+	position coord( p );
+	for ( char c : text )
+	{
+		m_terminal->print( coord, color, static_cast<unsigned char>( c ) );
+		++coord.x;
+		if ( coord.x >= m_terminal->get_size().x ) break;
+	}
+}
+
+void nv::ascii_printer::frame( const nv::rectangle& area, const nv::string_view& border_chars, uint32 color )
+{
+	if ( border_chars.length() < 8 )
+	{
+		m_terminal->clear( area );
+		return;
+	}
+	m_terminal->clear( area.shrinked( 1 ) );
+	for ( int x = 0; x < area.get_width(); ++x )
+	{
+		m_terminal->print( position( area.ul.x + x, area.ul.y ), color, border_chars[0] );
+		m_terminal->print( position( area.ul.x + x, area.lr.y ), color, border_chars[1] );
+	}
+
+	for ( int y = 0; y < area.get_height(); ++y )
+	{
+		m_terminal->print( position( area.ul.x, area.ul.y + y ), color, border_chars[2] );
+		m_terminal->print( position( area.lr.x, area.ul.y + y ), color, border_chars[3] );
+	}
+
+	m_terminal->print( area.ul, color, border_chars[4] );
+	m_terminal->print( area.ur(), color, border_chars[5] );
+	m_terminal->print( area.ll(), color, border_chars[6] );
+	m_terminal->print( area.lr, color, border_chars[7] );
+}
Index: trunk/src/engine/animation.cc
===================================================================
--- trunk/src/engine/animation.cc	(revision 486)
+++ trunk/src/engine/animation.cc	(revision 486)
@@ -0,0 +1,188 @@
+// Copyright (C) 2016-2016 ChaosForge Ltd
+// http://chaosforge.org/
+//
+// This file is part of Nova libraries. 
+// For conditions of distribution and use, see copying.txt file in root folder.
+
+#include "nv/engine/animation.hh"
+
+#include "nv/stl/range.hh"
+#include "nv/lua/lua_nova.hh"
+#include "nv/io/c_file_system.hh"
+#include "nv/formats/nmd_loader.hh"
+
+using namespace nv;
+
+nv::resource< animator_data > nv::animator_manager::load_animator( const nv::string_view& id, nv::pose_data_set* poses )
+{
+	lua::table_guard table( m_lua, lua::path( "animators", id ) );
+	return load_animator( table, id, poses );
+}
+
+bool nv::animator_manager::load_resource( nv::lua::table_guard& table, nv::shash64 id )
+{
+	return load_animator( table, id, nullptr ).is_valid();
+}
+
+nv::resource< animator_data > nv::animator_manager::load_animator( nv::lua::table_guard& table, nv::shash64 id, nv::pose_data_set* poses /*= nullptr */ )
+{
+	uint32 count = table.get_size();
+	if ( count == 0 )
+	{
+		return nv::resource< animator_data >();
+	}
+
+	nv::animator_data* animator = new nv::animator_data;
+	animator->poses = poses;
+	if ( poses == nullptr )
+	{
+		string128 poses_path = table.get_string128( "poses" );
+		if ( !poses_path.empty() )
+		{
+			nv::c_file_system fs;
+			nv::stream* poses_file = fs.open( poses_path );
+			nv::nmd_loader* ploader = new nv::nmd_loader( nullptr );
+			ploader->load( *poses_file );
+			delete poses_file;
+
+			animator->poses = ploader->release_pose_data_set();
+			NV_ASSERT( animator->poses, "NO POSES LOADED?" );
+		}
+	}
+
+	animator->layers.resize( count );
+	for ( auto layer_idx : nv::range( 1u, count ) )
+	{
+		nv::hash_store< shash64, uint32 > state_names;
+		nv::lua::table_guard layer_table( table, layer_idx );
+		nv::animator_layer_data& layer = animator->layers[layer_idx - 1];
+		layer.name = layer_table.get_string( "name", m_strings, 0 );
+		layer.mask = layer_table.get_integer( "mask", -1 );
+		layer.def_state = -1;
+
+		if ( layer.mask != -1 )
+		{
+			const auto& tree = animator->poses->get_tree();
+			layer.mask_vector.resize( tree.size(), false );
+			fill_mask_vector_rec( layer.mask_vector, tree, layer.mask );
+		}
+
+		if ( layer_table.is_table( "states" ) )
+		{
+			nv::lua::table_guard states_table( layer_table, "states" );
+			uint32 state_count = states_table.get_size();
+			if ( state_count > 0 )
+			{
+				layer.states.resize( state_count );
+
+				// Two passes for transition name resolution
+				for ( auto state_idx : nv::range( 1u, state_count ) )
+				{
+					nv::lua::table_guard state_table( states_table, state_idx );
+					nv::animator_state_data& state = layer.states[state_idx - 1];
+
+					state.name = state_table.get_string( "name", m_strings, 0 );
+					NV_ASSERT( state.name, "Animation state without name is invalid!" );
+					state_names[state.name] = state_idx - 1;
+					state.loop = state_table.get_boolean( "loop", true );
+					state.duration = state_table.get_float( "duration", 0.0f );
+					state.interp = nv::interpolation( state_table.get_unsigned( "interpolation", uint32( nv::interpolation::SPHERICAL ) ) );
+
+					shash64 pose_set = state_table.get_string_hash_64( "pose_set" );
+					if ( pose_set )
+					{
+						auto it = animator->poses->m_sets.find( pose_set );
+						NV_ASSERT( it->second.name, "POSE NOT FOUND" );
+						uint32 pid = it->second.start;
+						uint32 pose_count = it->second.count;
+						uint32 start = state_table.get_unsigned( "start", 0 );
+						uint32 stop = state_table.get_unsigned( "stop", pose_count - 1 );
+						for ( nv::uint32 i = pid + start; i < pid + stop + 1; ++i )
+						{
+							state.poses.push_back( nv::animator_pose_data{ i/*, 0.0f*/ } );
+						}
+
+						NV_ASSERT( state.poses.size() > 0, "POSES EMPTY!" );
+						state.base_pose = state.poses[0].pose;
+					}
+				}
+
+				// Second pass for transitions only
+				for ( auto state_idx : nv::range( 1u, state_count ) )
+				{
+					nv::animator_state_data& state = layer.states[state_idx - 1];
+					nv::lua::table_guard state_table( states_table, state_idx );
+
+					if ( state_table.is_table( "transitions" ) )
+					{
+						nv::lua::table_guard transitions_table( state_table, "transitions" );
+						uint32 transition_count = transitions_table.get_size();
+						if ( transition_count > 0 )
+						{
+							for ( auto transition_idx : nv::range( 1u, transition_count ) )
+							{
+								nv::lua::table_guard transition_table( transitions_table, transition_idx );
+								shash64 name = transition_table.get_string( "name", m_strings, 0 );
+								shash64 target_name = transition_table.get_string_hash_64( "target", 0 );
+								NV_ASSERT( name, "Transition state without name is invalid!" );
+								NV_ASSERT( target_name, "Transition state without name is invalid!" );
+								auto it = state_names.find( target_name );
+								NV_ASSERT( it != state_names.end(), "Transition target NOT FOUND!" );
+
+								nv::animator_transition_data tr_data;
+								tr_data.target = it->second;
+								tr_data.duration = transition_table.get_float( "duration", 0.0f );
+								tr_data.interp = nv::interpolation( transition_table.get_unsigned( "interpolation", uint32( nv::interpolation::SPHERICAL ) ) );
+								tr_data.easing = read_easing( transition_table );
+
+								state.transitions[name] = tr_data;
+							}
+						}
+					}
+				}
+			}
+		}
+
+
+
+
+		shash64 def_state_name = layer_table.get_string_hash_64( "default", 0 );
+		if ( def_state_name )
+		{
+			auto dsi = state_names.find( def_state_name );
+			NV_ASSERT( dsi != state_names.end(), "Unknown default!" );
+			layer.def_state = sint32( dsi->second );
+		}
+	}
+
+	return add( id, animator );
+}
+
+nv::easing animator_manager::read_easing( nv::lua::table_guard& table )
+{
+	nv::easing result;
+	result.in = nv::easing_type( table.get_integer( "easing", int( nv::easing_type::NONE ) ) );
+	result.in = nv::easing_type( table.get_integer( "ease_in", int( result.in ) ) );
+	result.out = nv::easing_type( table.get_integer( "ease_out", int( nv::easing_type::NONE ) ) );
+	if ( table.has_field( "ease_in_out" ) )
+	{
+		result.in = result.out = nv::easing_type( table.get_integer( "ease_in_out", int( nv::easing_type::NONE ) ) );
+	}
+	if ( result.in == nv::easing_type::NONE && result.out == nv::easing_type::NONE )
+	{
+		result.in = nv::easing_type::LINEAR;
+		result.out = nv::easing_type::NONE;
+	}
+	else
+	{
+		return result;
+	}
+	return result;
+}
+
+nv::resource< nv::animator_data > animator_manager::create_animator( const nv::string_view& id, nv::pose_data_set* poses )
+{
+	animator_data* data = new animator_data;
+	data->poses = poses;
+	return add( id, data );
+}
Index: trunk/src/engine/material_manager.cc
===================================================================
--- trunk/src/engine/material_manager.cc	(revision 485)
+++ trunk/src/engine/material_manager.cc	(revision 486)
@@ -12,4 +12,15 @@
 using namespace nv;
 
+
+nv::gpu_material_manager::gpu_material_manager( context* context, material_manager* matmgr, image_manager* imgmgr )
+	: m_context( context )
+	, m_material_manager( matmgr )
+	, m_image_manager( imgmgr )
+{
+	uint8 data[2 * 2 * 3];
+	nv::raw_fill_n( data, 2 * 2 * 3, 0 );
+	m_default = m_context->get_device()->create_texture( ivec2(2,2), nv::image_format( nv::RGB ), nv::sampler(), data );
+}
+
 bool gpu_material_manager::load_resource( const string_view& id )
 {
@@ -20,8 +31,16 @@
 		for ( uint32 i = 0; i < size( mat->paths ); ++i )
 			if ( !mat->paths[i].empty() )
+			{
 				if ( auto data = m_image_manager->get( mat->paths[i] ).lock() )
 				{
 					result->textures[i] = m_context->get_device()->create_texture( &*data, smp );
 				}
+			}
+
+		// HACK
+ 		for ( uint32 i = 0; i < 5; ++i )
+ 			if ( result->textures[i].is_nil() )
+ 				result->textures[i] = m_default;
+			
 		add( id, result );
 		return true;
@@ -44,4 +63,5 @@
 	m->paths[ TEX_SPECULAR ] = table.get_string128( "specular" );
 	m->paths[ TEX_NORMAL ]   = table.get_string128( "normal" );
+	m->paths[ TEX_EMISSIVE ] = table.get_string128( "emissive" );
 	m->paths[ TEX_GLOSS ]    = table.get_string128( "gloss" );
 	add( id, m );
Index: trunk/src/engine/resource_system.cc
===================================================================
--- trunk/src/engine/resource_system.cc	(revision 485)
+++ trunk/src/engine/resource_system.cc	(revision 486)
@@ -23,7 +23,7 @@
 }
 
-void nv::lua_resource_manager_base::load_all()
+void nv::lua_resource_manager_base::load_all( bool do_clear )
 {
-	clear();
+	if ( do_clear ) clear();
 	lua::table_guard table( m_lua, get_storage_name() );
 	uint32 count = table.get_unsigned( "__counter" );
Index: trunk/src/formats/assimp_loader.cc
===================================================================
--- trunk/src/formats/assimp_loader.cc	(revision 485)
+++ trunk/src/formats/assimp_loader.cc	(revision 486)
@@ -133,7 +133,7 @@
 	{
  		data_node_info info;
-		data_channel_set* data = data_channel_set_creator::create_set( 2 );
-		load_mesh_data( data, i, info );
-		m_meshes.push_back( data );
+		data_channel_set* mdata = data_channel_set_creator::create_set( 2 );
+		load_mesh_data( mdata, i, info );
+		m_meshes.push_back( mdata );
 		m_mesh_info.push_back( info );
 	}
@@ -184,5 +184,5 @@
 	int hack_for_node_anim;
 	if ( is_node_animated() )
-		info.parent_id = index;
+		info.parent_id = sint16( index );
 
 
@@ -339,5 +339,5 @@
 }
 
-void nv::assimp_loader::build_skeleton( vector< data_node_info >& skeleton, const void* node, int parent_id )
+void nv::assimp_loader::build_skeleton( vector< data_node_info >& skeleton, const void* node, sint16 parent_id )
 {
 	const aiNode* ainode = reinterpret_cast<const aiNode*>( node );
@@ -360,5 +360,5 @@
 	info.parent_id = parent_id;
 
-	int this_id = skeleton.size();
+	sint16 this_id = sint16( skeleton.size() );
 	skeleton.push_back( info );
 	for ( unsigned i = 0; i < ainode->mNumChildren; ++i )
@@ -428,5 +428,9 @@
 		{
 			mat4 tr = nv::math::inverse( assimp_mat4_cast( m_data->node_by_name[bone_data[i].name]->mTransformation ) );
-			bone_data[i].transform = tr * bone_data[bone_data[i].parent_id].transform;
+			int pid = bone_data[i].parent_id;
+			if ( pid != -1 )
+				bone_data[i].transform = tr * bone_data[pid].transform;
+			else
+				bone_data[i].transform = tr;
 		}
 //		list->append( bone_data[i] );
@@ -449,5 +453,4 @@
 	if ( scene->mRootNode == nullptr || scene->mAnimations == nullptr || scene->mAnimations[index] == nullptr) return nullptr;
 
-	const aiNode*      root = scene->mRootNode;
 	const aiAnimation* anim = scene->mAnimations[index];
 
@@ -481,5 +484,5 @@
 }
 
-data_node_list* nv::assimp_loader::release_data_node_list( size_t index /*= 0 */ )
+data_node_list* nv::assimp_loader::release_data_node_list( size_t /*= 0 */ )
 {
 	return release_merged_bones();
Index: trunk/src/formats/nmd_loader.cc
===================================================================
--- trunk/src/formats/nmd_loader.cc	(revision 485)
+++ trunk/src/formats/nmd_loader.cc	(revision 486)
@@ -261,5 +261,5 @@
 	nmd_element_header pheader;
 	pheader.type       = nv::nmd_type::POSES;
-	pheader.children   = poses.size();
+	pheader.children   = uint16( poses.size() );
 	pheader.size       = sizeof( transform ) * poses.size() * ( poses.size() > 0 ? poses[0]->size() : 0 )
 		               + sizeof( uint32 ) * poses.size();
Index: trunk/src/gfx/skeleton_instance.cc
===================================================================
--- trunk/src/gfx/skeleton_instance.cc	(revision 485)
+++ trunk/src/gfx/skeleton_instance.cc	(revision 486)
@@ -8,4 +8,5 @@
 
 #include "nv/core/profiler.hh"
+#include "nv/interface/interpolate.hh"
 
 void nv::skeleton_binding::assign( const skeleton_binding& other )
@@ -78,44 +79,4 @@
 }
 
-
-void nv::skeleton_instance::assign( const skeleton_transforms& skeleton, const skeleton_binding& binding, const bone_transforms& bones )
-{
-	if ( bones.size() != m_matrix.size() )
-		m_matrix.resize( bones.size() );
-	const transform* transforms = skeleton.xforms();
-	for ( uint32 n = 0; n < skeleton.size(); ++n )
-	{
-		sint16 bone_id = binding.m_indices[n];
-		if ( bone_id >= 0 )
-		{
-			int too_complex;
-			transform tr( bones.m_offsets[bone_id] );
-			tr.set_orientation( normalize( tr.get_orientation() ) );
-			m_matrix[bone_id] = ( transforms[n] * tr ).extract();
-		}
-	}
-}
-
-
-void nv::skeleton_instance::assign( const skeleton_transforms& skeleton, const bone_transforms& bones )
-{
-	if ( bones.size() != m_matrix.size() ) 
-		m_matrix.resize( bones.size() );
-	const transform* transforms = skeleton.xforms();
-	for ( uint32 n = 0; n < skeleton.size(); ++n )
-	{
- 		transform tr( bones.m_offsets[n] );
- 		tr.set_orientation( normalize( tr.get_orientation() ) );
- 		m_matrix[n] = ( transforms[n] * tr ).extract();
-	//	m_matrix[n] = transforms[n].extract() * bones.m_offsets[n];
-	}
-}
-
-void nv::skeleton_instance::assign( const bone_transforms& bones )
-{
-	if ( bones.size() != m_matrix.size() )
-		m_matrix.resize( bones.size() );
-}
-
 void nv::skeleton_transforms::assign( const data_node_list* node_data )
 {
@@ -125,136 +86,81 @@
 	for ( uint32 n = 0; n < node_data->size(); ++n )
 	{
-		const data_node_info& info = (*node_data)[ n ];
-		m_transforms[n] = transform( info.transform );
+		m_transforms[n] = transform( ( *node_data )[n].transform );
 	}
 }
 
-void nv::skeleton_transforms::interpolate_linear( const skeleton_transforms& a, const skeleton_transforms& b, float t )
+void nv::skeleton_transforms::assign( const skeleton_transforms& other, const array_view< bool >& mask )
+{
+	if ( m_transforms.size() != other.size() ) m_transforms.resize( other.size() );
+	if ( mask.size() == 0 )
+		m_transforms.assign( other.m_transforms );
+	else
+		for ( uint32 i = 0; i < other.size(); ++i )
+			if ( mask[i] )
+				m_transforms[i] = other.m_transforms[i];
+}
+
+void nv::skeleton_transforms::interpolate( const skeleton_transforms& a, const skeleton_transforms& b, float t, interpolation i )
+{
+	if ( m_transforms.size() != a.size() ) m_transforms.resize( a.size() );
+	::nv::interpolate( m_transforms, t, i, a.m_transforms, b.m_transforms );
+}
+
+void nv::skeleton_transforms::interpolate( const skeleton_transforms& a, const skeleton_transforms& b, float t, interpolation i, const array_view< bool >& mask )
+{
+	if ( m_transforms.size() != a.size() ) m_transforms.resize( a.size() );
+	if ( mask.size() > 0 )
+		::nv::interpolate( m_transforms, t, i, mask, a.m_transforms, b.m_transforms );
+	else
+		::nv::interpolate( m_transforms, t, i, a.m_transforms, b.m_transforms );
+}
+
+void nv::skeleton_transforms::interpolate( const skeleton_transforms& s1, const skeleton_transforms& v1, const skeleton_transforms& v2, const skeleton_transforms& s2, float t, interpolation i )
+{
+	if ( m_transforms.size() != s1.size() ) m_transforms.resize( s1.size() );
+	::nv::interpolate( m_transforms, t, i, s1.m_transforms, v1.m_transforms, v2.m_transforms, s2.m_transforms );
+}
+
+void nv::skeleton_transforms::interpolate( const skeleton_transforms& s1, const skeleton_transforms& v1, const skeleton_transforms& v2, const skeleton_transforms& s2, float t, interpolation i, const array_view< bool >& mask )
+{
+	if ( m_transforms.size() != s1.size() ) m_transforms.resize( s1.size() );
+	if ( mask.size() > 0 )
+		::nv::interpolate( m_transforms, t, i, mask, s1.m_transforms, v1.m_transforms, v2.m_transforms, s2.m_transforms );
+	else
+		::nv::interpolate( m_transforms, t, i, s1.m_transforms, v1.m_transforms, v2.m_transforms, s2.m_transforms );
+}
+
+void nv::skeleton_transforms::blend( const skeleton_transforms& a, const skeleton_transforms& b, float t, interpolation i, float blend, interpolation bi )
 {
 	NV_ASSERT( a.size() == b.size(), "!!!" );
 	if ( m_transforms.size() != a.size() )
 		m_transforms.resize( a.size() );
-	for ( uint32 n = 0; n < a.size(); ++n )
-	{
-		m_transforms[n] = transform(
-			math::mix( a.m_transforms[n].get_position(), b.m_transforms[n].get_position(), t ),
-			math::lerp( a.m_transforms[n].get_orientation(), b.m_transforms[n].get_orientation(), t )
-			);
-	}
-
-//  	if ( m_transforms.size() > 0 )
-//  		m_transforms[0] = nv::interpolate( a.m_transforms[0], b.m_transforms[0], t, interpolation::SPHERICAL );
+	::nv::interpolate( m_transforms, t, i, blend, bi, a.m_transforms, b.m_transforms );
 }
 
-void nv::skeleton_transforms::interpolate_nlerp( const skeleton_transforms& a, const skeleton_transforms& b, float t )
+void nv::skeleton_transforms::blend( const skeleton_transforms& a, const skeleton_transforms& b, float t, interpolation i, float blend, interpolation bi, const array_view< bool >& mask )
 {
 	NV_ASSERT( a.size() == b.size(), "!!!" );
 	if ( m_transforms.size() != a.size() )
 		m_transforms.resize( a.size() );
-
-	for ( uint32 n = 0; n < a.size(); ++n )
-	{
-		m_transforms[n] = transform(
-			math::mix( a.m_transforms[n].get_position(), b.m_transforms[n].get_position(), t ),
-			math::nlerp( a.m_transforms[n].get_orientation(), b.m_transforms[n].get_orientation(), t )
-			);
-	}
+	if ( mask.size() > 0 )
+		::nv::interpolate( m_transforms, t, i, blend, bi, mask, a.m_transforms, b.m_transforms );
+	else
+		::nv::interpolate( m_transforms, t, i, blend, bi, a.m_transforms, b.m_transforms );
 }
 
-
-void nv::skeleton_transforms::interpolate_slerp( const skeleton_transforms& a, const skeleton_transforms& b, float t )
+void nv::skeleton_transforms::blend( const skeleton_transforms& s1, const skeleton_transforms& v1, const skeleton_transforms& v2, const skeleton_transforms& s2, float t, interpolation i, float blend, interpolation bi )
 {
-	NV_ASSERT( a.size() == b.size(), "!!!" );
-	if ( m_transforms.size() != a.size() )
-		m_transforms.resize( a.size() );
-	for ( uint32 n = 0; n < a.size(); ++n )
-	{
-		m_transforms[n] = nv::interpolate( a.m_transforms[n], b.m_transforms[n], t, interpolation::SPHERICAL );
-	}
+	if ( m_transforms.size() != s1.size() ) m_transforms.resize( s1.size() );
+	::nv::interpolate( m_transforms, t, i, blend, bi, s1.m_transforms, v1.m_transforms, v2.m_transforms, s2.m_transforms );
 }
 
-void nv::skeleton_transforms::blend_slerp( const skeleton_transforms& a, const skeleton_transforms& b, float t, float blend )
+void nv::skeleton_transforms::blend( const skeleton_transforms& s1, const skeleton_transforms& v1, const skeleton_transforms& v2, const skeleton_transforms& s2, float t, interpolation i, float blend, interpolation bi, const array_view< bool >& mask )
 {
-	NV_ASSERT( a.size() == b.size(), "!!!" );
-	if ( m_transforms.size() != a.size() )
-		m_transforms.resize( a.size() );
-	for ( uint32 n = 0; n < a.size(); ++n )
-	{
-		transform tr    = nv::interpolate( a.m_transforms[n], b.m_transforms[n], t, interpolation::SPHERICAL );
-		m_transforms[n] = nv::interpolate( m_transforms[n], tr, blend, interpolation::SPHERICAL );
-	}
-}
-
-
-
-void nv::skeleton_transforms::interpolate4( const skeleton_transforms& s1, const skeleton_transforms& v1, const skeleton_transforms& v2, const skeleton_transforms& s2, float t )
-{
-	NV_ASSERT( s1.size() == s2.size(), "!!!" );
-	NV_ASSERT( v1.size() == v2.size(), "!!!" );
-	NV_ASSERT( s1.size() == v1.size(), "!!!" );
-	if ( m_transforms.size() != s1.size() )
-		m_transforms.resize( s1.size() );
-	float interp_squared = t*t;
-	float interp_cubed = interp_squared*t;
-	float weights[4];
-	weights[0] = 0.5f * ( -interp_cubed + 2.0f * interp_squared - t );
-	weights[1] = 0.5f * ( 3.0f * interp_cubed - 5.0f * interp_squared + 2.0f );
-	weights[2] = 0.5f * ( -3.0f * interp_cubed + 4.0f * interp_squared + t );
-	weights[3] = 0.5f * ( interp_cubed - interp_squared );
-
-	for ( uint32 n = 0; n < s1.size(); ++n )
-	{
-		quat qs1 = s1.m_transforms[n].get_orientation();
-		quat qs2 = s2.m_transforms[n].get_orientation();
-		quat qv1 = v1.m_transforms[n].get_orientation();
-		quat qv2 = v2.m_transforms[n].get_orientation();
-
-		float a = dot( qv1, qv2 ) > 0.0f ? 1.0f : -1.0f;
-
-		quat qr = weights[0] * qs1 
-				+ weights[1] * (a * qv1 )
-				+ weights[2] * qv2 
-				+ weights[3] * qs2;
-
-		qr = normalize( qr );
-
-		m_transforms[n] = transform(
-			weights[0] * s1.m_transforms[n].get_position() +
-			weights[1] * v1.m_transforms[n].get_position() +
-			weights[2] * v2.m_transforms[n].get_position() +
-			weights[3] * s2.m_transforms[n].get_position(),
-			qr
-		);
-	}
-}
-
-
-void nv::skeleton_transforms::interpolate_squad( const skeleton_transforms& s1, const skeleton_transforms& v1, const skeleton_transforms& v2, const skeleton_transforms& s2, float t )
-{
-	NV_ASSERT( s1.size() == s2.size(), "!!!" );
-	NV_ASSERT( v1.size() == v2.size(), "!!!" );
-	NV_ASSERT( s1.size() == v1.size(), "!!!" );
-	if ( m_transforms.size() != s1.size() )
-		m_transforms.resize( s1.size() );
-
-	for ( uint32 n = 0; n < s1.size(); ++n )
-	{
-		nv::quat ss1 = s1.m_transforms[n].get_orientation();
-		nv::quat ss2 = s2.m_transforms[n].get_orientation();
-		nv::quat sv1 = v1.m_transforms[n].get_orientation();
-		nv::quat sv2 = v2.m_transforms[n].get_orientation();
-
-		nv::quat q = normalize( nv::math::squad(
-			sv1, sv2,
-			nv::math::intermediate( ss1, sv1, sv2 ),
-			nv::math::intermediate( sv1, sv2, ss2 ),
-			t ) );
-
-		m_transforms[n] = transform(
-			mix( v1.m_transforms[n].get_position(), v2.m_transforms[n].get_position(), t ),
-			q
-			);
-	}
-	
+	if ( m_transforms.size() != s1.size() ) m_transforms.resize( s1.size() );
+	if ( mask.size() > 0 )
+		::nv::interpolate( m_transforms, t, i, blend, bi, mask, s1.m_transforms, v1.m_transforms, v2.m_transforms, s2.m_transforms );
+	else
+		::nv::interpolate( m_transforms, t, i, blend, bi, s1.m_transforms, v1.m_transforms, v2.m_transforms, s2.m_transforms );
 }
 
@@ -264,32 +170,4 @@
 }
 
-// void nv::skeleton_transforms::blend_local( const mesh_nodes_data* node_data, float frame, float blend )
-// {
-// 	if ( m_transforms.size() != node_data->size() )
-// 		m_transforms.resize( node_data->size() );
-// 	for ( uint32 n = 0; n < node_data->size(); ++n )
-// 	{
-// 		const data_channel_set* node = ( *node_data )[n];
-// 		int inefficient_store_key;
-// 
-// 		transform tr = node->size() > 0 ? raw_channel_interpolator( node ).get< transform >( frame ) : transform( /*node->get_transform()*/ ); int confirm_that_not_needed;
-// 		m_transforms[n] = nv::interpolate( m_transforms[n], tr, blend, interpolation::SPHERICAL );
-// 	}
-// }
-// 
-// void nv::skeleton_transforms::animate_local( const mesh_nodes_data* node_data, float frame )
-// {
-// 	if ( m_transforms.size() != node_data->size() )
-// 		m_transforms.resize( node_data->size() );
-// 	for ( uint32 n = 0; n < node_data->size(); ++n )
-// 	{
-// 		const data_channel_set* node = ( *node_data )[n];
-// 		if ( node->size() > 0 )
-// 		{
-// 			int inefficient_store_key;
-// 			m_transforms[n] = raw_channel_interpolator( node ).get< transform >( frame );
-// 		}
-// 	}
-// }
 
 void nv::skeleton_transforms::delocalize_rec( const data_node_tree& node_data, uint32 id, const transform& parent )
@@ -304,51 +182,2 @@
 }
 
-// void nv::skeleton_transforms::blend_rec( const mesh_nodes_data* node_data, 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 )
-// 	{
-// 		int inefficient_store_key;
-// 
-// 		raw_channel_interpolator interpolator( node );
-// 		node_mat = interpolator.get< transform >( frame );
-// 	}
-// 	transform global_mat = parent * node_mat;
-// 	m_transforms[id] = nv::interpolate( m_transforms[id], local ? node_mat : global_mat, blend, interpolation::SPHERICAL );
-// 	for ( auto child : node_data->children( id ) )
-// 	{
-// 		blend_rec( node_data, frame, child, global_mat, local, blend );
-// 	}
-// 
-// }
-// 
-// void nv::skeleton_transforms::animate_rec( const mesh_nodes_data* node_data, float frame, uint32 id, const transform& parent, bool local )
-// {
-// 	const data_channel_set* node = ( *node_data )[id];
-// 	transform node_mat;
-// 	int inefficient_store_key;
-// 
-// 	if ( node->size() > 0 )
-// 		node_mat = raw_channel_interpolator( node ).get< transform >( frame );
-// 	int confirm_that_not_needed;
-// 	// 	else
-// 	// 		node_mat = transform( node->get_transform() );
-// 	transform global_mat = parent * node_mat;
-// 	m_transforms[id] = local ? node_mat : global_mat;
-// 	for ( auto child : node_data->children( id ) )
-// 	{
-// 		animate_rec( node_data, frame, child, global_mat, local );
-// 	}
-// 
-// }
-
-void nv::bone_transforms::prepare( const data_node_list& bone_data )
-{
-	m_offsets.resize( bone_data.size() );
-
-	for ( nv::uint16 bi = 0; bi < bone_data.size(); ++bi )
-		m_offsets[bi] = bone_data[bi].transform;
-}
Index: trunk/src/image/miniz.cc
===================================================================
--- trunk/src/image/miniz.cc	(revision 485)
+++ trunk/src/image/miniz.cc	(revision 486)
@@ -813,5 +813,5 @@
 	static void *def_alloc_func( void *opaque, size_t items, size_t size ) { (void)opaque, (void)items, (void)size; return MZ_MALLOC( items * size ); }
 	static void def_free_func( void *opaque, void *address ) { (void)opaque, (void)address; MZ_FREE( address ); }
-	static void *def_realloc_func( void *opaque, void *address, size_t items, size_t size ) { (void)opaque, (void)address, (void)items, (void)size; return MZ_REALLOC( address, items * size ); }
+//	static void *def_realloc_func( void *opaque, void *address, size_t items, size_t size ) { (void)opaque, (void)address, (void)items, (void)size; return MZ_REALLOC( address, items * size ); }
 
 	const char *mz_version( void )
Index: trunk/src/image/png_loader.cc
===================================================================
--- trunk/src/image/png_loader.cc	(revision 485)
+++ trunk/src/image/png_loader.cc	(revision 486)
@@ -665,7 +665,9 @@
 }
 
+
 static int stbi__unpremultiply_on_load = 0;
 static int stbi__de_iphone_flag = 0;
 
+/*
 static void stbi_set_unpremultiply_on_load( int flag_true_if_should_unpremultiply )
 {
@@ -677,4 +679,5 @@
 	stbi__de_iphone_flag = flag_true_if_should_convert;
 }
+*/
 
 static void stbi__de_iphone( stbi__png *z )
@@ -1021,4 +1024,10 @@
 }
 
+bool nv::png_loader::test( stream& str )
+{
+	stbi__context s( &stbi__callbacks, (void *)&str );
+	return stbi__png_test( &s ) != 0;
+}
+
 image_data* nv::png_loader::load( stream& s )
 {
Index: trunk/src/io/c_stream.cc
===================================================================
--- trunk/src/io/c_stream.cc	(revision 485)
+++ trunk/src/io/c_stream.cc	(revision 486)
@@ -98,5 +98,6 @@
 	if ( m_file != nullptr )
 	{
-		return ::feof( reinterpret_cast<FILE*>( m_file ) );
+		return ::feof( reinterpret_cast<FILE*>( m_file ) ) != 0;
 	}
+	return true;
 }
Index: trunk/src/lua/lua_state.cc
===================================================================
--- trunk/src/lua/lua_state.cc	(revision 485)
+++ trunk/src/lua/lua_state.cc	(revision 486)
@@ -194,4 +194,5 @@
 		str = lua_tolstring( m_state, -1, &l );
 		result = hash_string< uint64 >( str, l );
+		//NV_LOG_DEBUG( str );
 	}
 	lua_pop( m_state, 1 );
@@ -204,12 +205,28 @@
 	size_t l = 0;
 	const char* str = nullptr;
-	uint64 result = defval;
+	shash64 result = shash64( defval );
 	if ( lua_type( m_state, -1 ) == LUA_TSTRING )
 	{
 		str = lua_tolstring( m_state, -1, &l );
-		result = table.insert( string_view( str, l ) ).value();
-	}
-	lua_pop( m_state, 1 );
-	return shash64( result );
+		result = table.insert( string_view( str, l ) );
+	}
+	lua_pop( m_state, 1 );
+	return result;
+}
+
+nv::shash64 nv::lua::table_guard::get_string( string_view element, string_table* table, uint64 defval /*= 0 */ )
+{
+	lua_getfield( m_state, -1, element.data() );
+	size_t l = 0;
+	const char* str = nullptr;
+	shash64 result = shash64( defval );
+	if ( lua_type( m_state, -1 ) == LUA_TSTRING )
+	{
+		str = lua_tolstring( m_state, -1, &l );
+		string_view sv( str, l );
+		result = table ? table->insert( sv ) : shash64( sv );
+	}
+	lua_pop( m_state, 1 );
+	return result;
 }
 
Index: trunk/src/sdl/sdl_window.cc
===================================================================
--- trunk/src/sdl/sdl_window.cc	(revision 485)
+++ trunk/src/sdl/sdl_window.cc	(revision 486)
@@ -45,13 +45,38 @@
 	}
 
-	// 	NV_LOG( LOG_INFO, "Joystick count : " << SDL_NumJoysticks() );
-	// 	SDL_Joystick* j = SDL_JoystickOpen(0);
-	// 	if (j)
-	// 	{
-	// 		NV_LOG( LOG_INFO, "Joystick Name: " << SDL_JoystickNameForIndex(0) );
-	// 		NV_LOG( LOG_INFO, "Joystick Number of Axes: " << SDL_JoystickNumAxes(j));
-	// 		NV_LOG( LOG_INFO, "Joystick Number of Buttons: " << SDL_JoystickNumButtons(j));
-	// 		NV_LOG( LOG_INFO, "Joystick Number of Balls: " << SDL_JoystickNumBalls(j));
-	// 	}
+// 	NV_LOG_INFO( "Joystick count : ", SDL_NumJoysticks() );
+// 	SDL_Joystick* j = SDL_JoystickOpen(0);
+// 	if ( j )
+// 	{
+// 		NV_LOG_INFO( "Joystick Name: ", SDL_JoystickNameForIndex( 0 ) );
+// 		NV_LOG_INFO( "Joystick Number of Axes: ", SDL_JoystickNumAxes( j ) );
+// 		NV_LOG_INFO( "Joystick Number of Buttons: ", SDL_JoystickNumButtons( j ) );
+// 		NV_LOG_INFO( "Joystick Number of Balls: ", SDL_JoystickNumBalls( j ) );
+// 	}
+
+ 	NV_LOG_INFO( "Joystick count : ", SDL_NumJoysticks() );
+	SDL_GameController *controller = NULL;
+	int controller_index = 0;
+	for ( int i = 0; i < SDL_NumJoysticks(); ++i )
+	{
+		if ( SDL_IsGameController( i ) )
+		{
+			controller = SDL_GameControllerOpen( i );
+			controller_index = i;
+			if ( controller ) break;
+			NV_LOG_ERROR( "Could not open gamecontroller ", i, ": ", SDL_GetError() );
+		}
+	}
+
+	if ( controller )
+	{
+		SDL_Joystick* j = SDL_GameControllerGetJoystick( controller );
+		NV_LOG_INFO( "Controller Name: ", SDL_GameControllerNameForIndex( controller_index ) );
+		NV_LOG_INFO( "Controller Number of Axes: ", SDL_JoystickNumAxes( j ) );
+		NV_LOG_INFO( "Controller Number of Buttons: ", SDL_JoystickNumButtons( j ) );
+		NV_LOG_INFO( "Controller Number of Balls: ", SDL_JoystickNumBalls( j ) );
+	}
+
+
 
 	void* ctx_handle = SDL_GL_CreateContext( static_cast<SDL_Window*>( m_handle ) );
