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" );
