Index: /trunk/nv.lua
===================================================================
--- /trunk/nv.lua	(revision 485)
+++ /trunk/nv.lua	(revision 486)
@@ -117,8 +117,11 @@
 		if _ACTION == "gmake-clang" then
 			buildoptions { 
-				"-std=c++11",
+				"-std=c++1z",
 --                                -- on Mac OS X don't try to use old stdc++
 --                                "-stdlib=libc++",
 				"-Weverything",
+				-- math is so much easier with these
+				"-Wno-gnu-anonymous-struct", 
+				"-Wno-nested-anon-types", 
 				-- obviously we don't care about C++98 compatibility
 				"-Wno-c++98-compat", 
Index: /trunk/nv/base/common.hh
===================================================================
--- /trunk/nv/base/common.hh	(revision 485)
+++ /trunk/nv/base/common.hh	(revision 486)
@@ -266,4 +266,5 @@
 	constexpr T narrow_cast( const U& a )
 	{
+		NV_MSVC_SUPRESS( 4806 );
 		return static_cast<T>( a & static_cast<T>( -1 ) );
 	}
Index: /trunk/nv/core/transform.hh
===================================================================
--- /trunk/nv/core/transform.hh	(revision 485)
+++ /trunk/nv/core/transform.hh	(revision 486)
@@ -17,4 +17,6 @@
 	{
 	public:
+		typedef float value_type;
+
 		transform() {}
 		explicit transform( const vec3& a_position ) : m_position( a_position ) {}
@@ -106,4 +108,17 @@
 	template <> struct type_to_enum< transform > { static const datatype type = TRANSFORM; };
 
+	namespace math
+	{
+
+		inline transform lerp( const transform& a, const transform& b, float value )
+		{
+			return transform(
+				math::lerp( a.get_position(), b.get_position(), value ),
+				math::lerp( a.get_orientation(), b.get_orientation(), value )
+				);
+		}
+
+	}
+
 }
 
Index: /trunk/nv/engine/animation.hh
===================================================================
--- /trunk/nv/engine/animation.hh	(revision 485)
+++ /trunk/nv/engine/animation.hh	(revision 486)
@@ -17,11 +17,13 @@
 #include <nv/core/resource.hh>
 #include <nv/interface/mesh_data.hh>
+#include <nv/interface/easing.hh>
 #include <nv/gfx/skeleton_instance.hh>
 #include <nv/gfx/poses.hh>
+#include <nv/engine/resource_system.hh>
 
 namespace nv
 {
 	class animator_bind_data;
-	class animator_data;
+	struct animator_data;
 }
 
@@ -33,35 +35,113 @@
 {
 
-	struct animator_clip_data
-	{
-		uint32      id;
-		frame_range range;
-	};
-
-	class animator_data
+	struct animator_pose_data
+	{
+		uint32 pose;
+		// float  time;
+		//	float      easing_data[4];
+	};
+
+	struct animator_transition_data
+	{
+		uint32        target;
+		float         duration;
+		interpolation interp;
+		easing        easing;
+	};
+
+	typedef hash_store< shash64, animator_transition_data > animator_transition_table;
+
+	struct animator_state_data
+	{
+		shash64                      name;
+		uint32                       base_pose;
+		vector< animator_pose_data > poses;
+		bool                         loop;
+		float                        duration;
+		interpolation                interp;
+		animator_transition_table    transitions;
+	};
+
+	struct animator_layer_data
+	{
+		shash64 name;
+		sint32  def_state;
+		sint32  mask;
+		vector< bool >                mask_vector;
+		vector< animator_state_data > states;
+	};
+
+	struct animator_data
+	{
+		vector< animator_layer_data > layers;
+		pose_data_set*                poses;
+	};
+
+	struct animator_transition_instance : animator_transition_data
+	{
+		uint32        source;
+		float         time;
+
+		animator_transition_instance() {}
+
+		animator_transition_instance( const animator_transition_data& data, uint32 source )
+			: animator_transition_data( data ), source( source ), time( 0.0f )
+		{
+
+		}
+	};
+
+	class animator_layer_instance
 	{
 	public:
-		animator_data( pose_data_set* data ) : m_data( data ) {}
-		//
-		pose_data_set* get_pose_data() { return m_data;  }
-		const pose_data_set* get_pose_data() const { return m_data; }
-
-		const animator_clip_data* get_clip( shash64 name ) const
-		{
-			auto it = m_clip_data.find( name );
-			return ( it != m_clip_data.end() ? &it->second : nullptr );
-		}
-		bool has_clip( shash64 name ) const
-		{
-			auto it = m_clip_data.find( name );
-			return it != m_clip_data.end();
-		}
-		void add_clip( shash64 name, animator_clip_data&& entry )
-		{
-			m_clip_data.assign( name, entry );
-		}
-	protected:
-		pose_data_set* m_data;
-		hash_store< shash64, animator_clip_data > m_clip_data;
+		vector< animator_transition_instance > m_transitions;
+		uint32 m_current_state;
+		float m_time;
+
+		animator_layer_instance() : m_current_state( 0 ), m_time( 0.0f ) {}
+		void set_state( uint32 state ) { m_current_state = state; }
+		uint32 get_state() const { return m_current_state; }
+		uint32 get_final_state() const
+		{
+			if ( m_transitions.size() > 0 ) 
+				return m_transitions.back().target;
+			return m_current_state;
+		}
+		void add_transition( const animator_transition_data& data, uint32 source )
+		{
+			if ( m_transitions.size() > 0 )
+			{
+				auto& last = m_transitions.back();
+				if ( last.target == source && data.target == last.source )
+				{
+					nv::swap( last.target, last.source );
+					last.time = last.duration - last.time;
+					return;
+				}
+			}
+			m_transitions.push_back( animator_transition_instance( data, source ) );
+		}
+		void reset()
+		{
+			m_transitions.clear();
+			m_time = 0.0f;
+		}
+		void update( float dtime )
+		{
+			if ( m_current_state != -1 )
+			{
+				float time = dtime;
+				while ( m_transitions.size() > 0 )
+				{
+					animator_transition_instance& tr = m_transitions.front();
+					m_current_state = tr.target;
+					tr.time += time;
+					if ( tr.time < tr.duration ) break;
+					time -= ( tr.time - tr.duration );
+					m_transitions.erase( m_transitions.begin() );
+				}
+				m_time += dtime;
+			}
+		}
 	};
 
@@ -77,5 +157,5 @@
 			m_pose_binding.prepare( pose_frame, bones );
 		}
-		const bone_transforms& get_bone_transforms() const { return m_bone_transforms; }
+		const bone_transforms< transform >& get_bone_transforms() const { return m_bone_transforms; }
 		const skeleton_binding& get_pose_binding() const
 		{
@@ -86,5 +166,5 @@
 	protected:
 		skeleton_binding                        m_pose_binding;
-		bone_transforms                         m_bone_transforms;
+		bone_transforms< transform >            m_bone_transforms;
 		data_node_list                          m_bone_list;
 	};
@@ -95,88 +175,202 @@
 	public:
 		animator_instance() {}
-		void initialize( resource< animator_data > data, resource< animator_bind_data > bind_data )
-		{
-			m_data = data;
-			m_bind_data = bind_data;
-		}
-		bool is_valid() const { return m_data.is_valid() && m_bind_data.is_valid(); }
-		bool has_clip( shash64 id ) const
-		{
-			if ( auto data = m_data.lock() )
-				return data->has_clip( id );
+		void initialize( const animator_data* data )
+		{
+			m_layers.clear();
+			m_layers.resize( data->layers.size() );
+			for ( uint32 i = 0; i < data->layers.size(); ++i )
+			{
+				m_layers[i].set_state( data->layers[i].def_state );
+			}
+		}
+
+		const skeleton_transforms& get_transforms() const
+		{
+			return m_transforms;
+		}
+
+		uint32 base_state() const
+		{
+			// HACK
+			if ( m_layers.size() > 1 && m_layers[0].m_transitions.size() < 1 ) return m_layers[0].get_state();
+			return 0;
+		}
+		
+		void queue_event( const animator_data* data, shash64 name )
+		{
+			for ( uint32 i = 0; i < m_layers.size(); ++i )
+				// what about activating nonactive layers?
+				if ( m_layers[i].m_current_state != -1 )
+				{
+					uint32 last_state = m_layers[i].get_final_state();
+					const animator_state_data& state = data->layers[i].states[last_state];
+					auto it = state.transitions.find( name );
+					if ( it != state.transitions.end() )
+					{
+						m_layers[i].add_transition( it->second, last_state );
+					}
+				}
+		}
+
+		void update( const animator_data* data, float dtime )
+		{
+			for ( uint32 i = 0; i < m_layers.size(); ++i )
+				m_layers[i].update( dtime );
+
+			for ( uint32 i = 0; i < m_layers.size(); ++ i )
+				if ( m_layers[i].m_current_state != -1 )
+				{
+					if ( m_layers[i].m_transitions.size() > 0 )
+					{
+						animate_layer( data->layers[i], m_layers[i].m_transitions.front(), data->poses, m_layers[i].m_time );
+					}
+					else
+					{
+						const animator_layer_data& layer = data->layers[i];
+						const animator_state_data& state = layer.states[m_layers[i].m_current_state];
+						if ( state.poses.size() > 0 )
+							animate_state( m_transforms, state, data->layers[i], data->poses, get_frame( state, m_layers[i].m_time ) );
+					}
+				}
+
+			m_transforms.delocalize( data->poses->get_tree() );
+		}
+
+		void reset()
+		{
+			for ( uint32 i = 0; i < m_layers.size(); ++i )
+				m_layers[i].reset();
+		}
+
+		void update_state_only( const animator_data* data, float dtime, uint32 layer_id, uint32 state_id )
+		{
+			const animator_layer_data& layer = data->layers[layer_id];
+			const animator_state_data& state = layer.states[state_id];
+			m_layers[layer_id].update( dtime );
+			if ( state.poses.size() > 0 )
+				animate_state( m_transforms, state, layer, data->poses, get_frame( state, m_layers[layer_id].m_time ) );
+			m_transforms.delocalize( data->poses->get_tree() );
+		}
+
+		void update_layer_only( const animator_data* data, float dtime, uint32 layer_id )
+		{
+			const animator_layer_data& ldata     = data->layers[layer_id];
+			const animator_layer_instance& linst = m_layers[layer_id];
+			m_layers[layer_id].update( dtime );
+			if ( m_layers[layer_id].m_transitions.size() > 0 )
+			{
+				animate_layer( ldata, linst.m_transitions.front(), data->poses, linst.m_time );
+			}
 			else
-				return false;
-		}
-		mat4 get_transform( uint32 idx ) const
-		{
-			if ( idx < m_skeleton.size() ) return m_skeleton.transforms()[idx];
-			return mat4();
-		}
-		const skeleton_instance& get_skeleton() const
-		{
-			return m_skeleton;
-		}
-
-		sint16 get_bone_index( shash64 bone_name ) const
-		{
-			if ( is_valid() )
-			{
-				auto data = m_bind_data.lock();
-				sint16 id = data->get_bone_list().resolve( bone_name );
-				return id;
-			}
-			return -1;
-		}
-
-		const mat4& get_bone_matrix( shash64 bone_name ) const
-		{
-			if ( is_valid() )
-			{
-				auto data = m_bind_data.lock();
-				sint16 id = data->get_bone_list().resolve( bone_name );
-				if ( id >= 0 )
-				{
-					return data->get_bone_list()[id].transform;
-				}
-			}
-			return mat4();
-		}
-
-
-		void update( shash64 clip, uint32 time )
-		{
-			if ( is_valid() )
-			{
-				auto data = m_data.lock();
-				auto bind_data = m_bind_data.lock();
-				const animator_clip_data* clip_data = data->get_clip( clip );
-
-				if ( data->get_pose_data() )
-				{
-					if ( clip_data )
-					{
-						auto pose_data = data->get_pose_data();
-						float fframe = clip_data->range.frame_from_ms_fps( time, 30 );
-
-						unsigned v1 = unsigned( fframe );
-						unsigned v2 = v1 + 1;
-						if ( v2 >= clip_data->range.end ) v2 = clip_data->range.start;
-
-						unsigned iv1 = v1 + clip_data->id;
-						unsigned iv2 = v2 + clip_data->id;
-
-						m_transforms.interpolate_slerp( pose_data->get( iv1 ), pose_data->get( iv2 ), fframe - v1 );
-						m_transforms.delocalize( pose_data->get_tree() );
-
-					}
-					m_skeleton.assign( m_transforms, bind_data->get_pose_binding(), bind_data->get_bone_transforms() );
-				}
-			}
-		}
+			{
+				const animator_state_data& state = ldata.states[linst.m_current_state];
+				if ( state.poses.size() > 0 )
+					animate_state( m_transforms, state, ldata, data->poses, get_frame( state, linst.m_time ) );
+			}
+			m_transforms.delocalize( data->poses->get_tree() );
+		}
+
 	protected:
-		resource< animator_data >      m_data;
-		resource< animator_bind_data > m_bind_data;
-		skeleton_transforms            m_transforms;
-		skeleton_instance              m_skeleton;
+
+		void animate_layer( const animator_layer_data& layer, const animator_transition_instance& transition, pose_data_set* pose_data, float t )
+		{
+			const animator_state_data& base_state = layer.states[transition.source];
+			if ( base_state.poses.size() > 0 )
+				animate_state( m_transforms, base_state, layer, pose_data, get_frame( base_state, t ) );
+
+			float trblend = 0.0f;
+			if ( transition.duration > 0.0f )
+			{
+				trblend = math::clamp( transition.time / transition.duration, 0.0f, 1.0f );
+				trblend = easing_eval( trblend, transition.easing );
+			}
+
+			const animator_state_data& state = layer.states[transition.target];
+			if ( state.poses.size() > 0 )
+				animate_state( m_transforms, state, layer, pose_data, get_frame( state, t ), true, trblend, transition.interp );
+		}
+
+		static float get_frame( const animator_state_data& state, float layer_time )
+		{
+			uint32 fcount = state.poses.size();
+			float fframe = 0.0f;
+			if ( state.duration > nv::math::epsilon<float>() )
+			{
+				float fps = fcount / state.duration;
+				float ctime = state.loop ? fmodf( layer_time, state.duration ) : min( layer_time, state.duration - 0.0001f );
+				fframe = ctime * fps; 
+			}
+			return fframe;
+		}
+
+		static void animate_state( skeleton_transforms& transforms, const animator_state_data& state, const animator_layer_data& layer, const pose_data_set* pose_data, float fframe, bool do_blend = false, float blend = 0.0f, nv::interpolation binterp = interpolation::NONE )
+		{
+			unsigned v1 = unsigned( fframe );
+			unsigned v2 = v1 + 1;
+			if ( v2 >= state.poses.size() ) v2 = state.loop ? 0 : state.poses.size() - 1;
+
+			unsigned iv1 = state.poses[v1].pose;
+			unsigned iv2 = state.poses[v2].pose;
+
+
+			if ( iv1 == iv2 )
+			{
+				if ( do_blend )
+					transforms.interpolate( transforms, pose_data->get( iv1 ), blend, binterp, layer.mask_vector );
+				else
+					transforms.assign( pose_data->get( iv1 ), layer.mask_vector );
+			}
+			else
+			{
+				if ( state.interp == interpolation::QUADRATIC || state.interp == interpolation::SQUADRATIC )
+				{
+					unsigned s1 = v1 == 0 ? state.poses.size() - 1 : v1 - 1; 
+					unsigned s2 = v2 + 1;
+					if ( s2 >= state.poses.size() ) s2 = state.loop ? 0 : state.poses.size() - 1;
+					unsigned is1 = state.poses[s1].pose;
+					unsigned is2 = state.poses[s2].pose;
+					if ( do_blend )
+						transforms.blend( pose_data->get( is1 ), pose_data->get( iv1 ), pose_data->get( iv2 ), pose_data->get( is2 ), fframe - v1, state.interp, blend, binterp, layer.mask_vector );
+					else
+						transforms.interpolate( pose_data->get( is1 ), pose_data->get( iv1 ), pose_data->get( iv2 ), pose_data->get( is2 ), fframe - v1, state.interp, layer.mask_vector );
+				}
+				else
+				{
+					if ( do_blend )
+						transforms.blend( pose_data->get( iv1 ), pose_data->get( iv2 ), fframe - v1, state.interp, blend, binterp, layer.mask_vector );
+					else
+						transforms.interpolate( pose_data->get( iv1 ), pose_data->get( iv2 ), fframe - v1, state.interp, layer.mask_vector );
+				}	
+			}
+		}
+
+		vector< animator_layer_instance > m_layers;
+		skeleton_transforms               m_transforms;
+
+
+	};
+
+	class animator_manager : public nv::lua_resource_manager< animator_data >
+	{
+	public:
+		explicit animator_manager( nv::string_table* strings = nullptr ) : m_strings( strings ) {}
+		virtual nv::string_view get_storage_name() const { return "animators"; }
+		virtual nv::string_view get_resource_name() const { return "animator"; }
+
+		nv::resource< animator_data > load_animator( const nv::string_view& id, nv::pose_data_set* poses = nullptr );
+		nv::resource< animator_data > create_animator( const nv::string_view& id, nv::pose_data_set* poses = nullptr );
+	protected:
+		virtual bool load_resource( nv::lua::table_guard& table, nv::shash64 id );
+		nv::resource< animator_data > load_animator( nv::lua::table_guard& table, nv::shash64 id, nv::pose_data_set* poses = nullptr );
+
+		nv::easing read_easing( nv::lua::table_guard& table );
+		void fill_mask_vector_rec( nv::vector< bool >& mask_vector, const nv::data_node_tree& tree, nv::uint32 id )
+		{
+			mask_vector[id] = true;
+			for ( auto child : tree.children( id ) )
+				fill_mask_vector_rec( mask_vector, tree, child );
+		}
+		
+		nv::string_table* m_strings;
 	};
 
Index: /trunk/nv/engine/material_manager.hh
===================================================================
--- /trunk/nv/engine/material_manager.hh	(revision 485)
+++ /trunk/nv/engine/material_manager.hh	(revision 486)
@@ -51,13 +51,10 @@
 	{
 	public:
-		gpu_material_manager( context* context, material_manager* matmgr, image_manager* imgmgr )
-			: m_context( context )
-			, m_material_manager( matmgr )
-			, m_image_manager( imgmgr )
-		{}
+		gpu_material_manager( context* context, material_manager* matmgr, image_manager* imgmgr );
 	protected:
 		virtual bool load_resource( const string_view& id );
 		virtual void release( gpu_material* m );
 	private:
+		texture           m_default;
 		context*          m_context;
 		material_manager* m_material_manager;
Index: /trunk/nv/engine/resource_system.hh
===================================================================
--- /trunk/nv/engine/resource_system.hh	(revision 485)
+++ /trunk/nv/engine/resource_system.hh	(revision 486)
@@ -55,5 +55,5 @@
 		virtual string_view get_resource_name() const = 0;
 		virtual void clear() = 0;
-		void load_all();
+		void load_all( bool do_clear = true );
 		virtual bool load_resource( const string_view& id );
 		virtual ~lua_resource_manager_base() {}
Index: /trunk/nv/formats/assimp_loader.hh
===================================================================
--- /trunk/nv/formats/assimp_loader.hh	(revision 485)
+++ /trunk/nv/formats/assimp_loader.hh	(revision 486)
@@ -34,5 +34,5 @@
 	private:
 		bool is_node_animated();
-		void build_skeleton( vector< data_node_info >& skeleton, const void* node, int parent_id );
+		void build_skeleton( vector< data_node_info >& skeleton, const void* node, sint16 parent_id );
 		data_node_list* release_merged_bones();
 		void load_mesh_data( data_channel_set* data, size_t index, data_node_info& info );
Index: /trunk/nv/gfx/poses.hh
===================================================================
--- /trunk/nv/gfx/poses.hh	(revision 486)
+++ /trunk/nv/gfx/poses.hh	(revision 486)
@@ -0,0 +1,120 @@
+// 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.
+
+#ifndef NV_GFX_POSES_HH
+#define NV_GFX_POSES_HH
+
+#include <nv/common.hh>
+#include <nv/stl/vector.hh>
+#include <nv/stl/array.hh>
+#include <nv/stl/math.hh>
+#include <nv/stl/string.hh>
+#include <nv/stl/hash_store.hh>
+#include <nv/interface/mesh_data.hh>
+#include <nv/interface/data_channel.hh>
+#include <nv/interface/interpolation_raw.hh>
+#include <nv/interface/interpolation_template.hh>
+#include <nv/core/transform.hh>
+
+namespace nv
+{
+	
+	class pose_data_set
+	{
+	public:
+		struct pose_set
+		{
+			uint32  start;
+			uint32  count;
+			shash64 name;
+		};
+
+		pose_data_set() : m_node_tree( shash64() ) {}
+		uint32 size() const { return m_data.size(); }
+		void initialize( const data_node_list& info )
+		{
+			NV_ASSERT_ALWAYS( m_node_tree.empty(), "Reinitialize!" );
+			m_node_tree.assign( info );
+			m_node_tree.initialize();
+		}
+		const data_node_tree& get_tree() const { return m_node_tree; }
+
+		const skeleton_transforms& get( uint32 pose ) const
+		{
+			return m_data[pose];
+		}
+
+
+		uint32 add_poses( string_view id, const mesh_nodes_data& data )
+		{
+			uint32 nid = m_data.size();
+			for ( int frame = 0; frame < data.get_frame_count(); frame++ )
+			{
+				add_pose( id, data, frame );
+			}
+			return nid;
+		}
+
+		uint32 add_pose( shash64 id, const mesh_nodes_data& data, int frame )
+		{
+			bool match = false;
+			uint32 nid = m_data.size();
+
+			auto& pset = m_sets[id];
+			if ( !pset.name )
+			{
+				pset.start = nid;
+				pset.count = 1;
+				pset.name = id;
+			}
+			else
+				pset.count++;
+
+			m_data.push_back( skeleton_transforms() );
+			auto& pose = m_data.back();
+
+			if ( m_node_tree.empty() )
+			{
+				initialize( data.get_info() );
+				match = true;
+			}
+			else
+			{
+				match = m_node_tree.matches( data.get_info() );
+				//NV_ASSERT_ALWAYS( match, "mismatch!" );
+			}
+
+			pose.m_transforms.resize( m_node_tree.size() );
+
+			for ( sint32 i = 0; i < sint32( m_node_tree.size() ); ++i )
+			{
+				const data_channel_set* node = nullptr;
+				if ( match )
+				{
+					node = data[i];
+				}
+				else
+				{
+					node = data.get_by_hash( m_node_tree[i].name );
+				}
+				if ( node )
+				{
+					transform tf = raw_channel_interpolator( node ).get< transform >( float( frame ) );
+					pose.m_transforms[i] = tf/*.get_orientation()*/;
+				}
+			}
+			return nid;
+		}
+
+
+		data_node_tree m_node_tree;
+		vector< skeleton_transforms > m_data;
+		hash_store< shash64, pose_set > m_sets;
+	};
+
+}
+
+#endif // NV_GFX_POSES_HH
Index: /trunk/nv/gfx/skeleton_instance.hh
===================================================================
--- /trunk/nv/gfx/skeleton_instance.hh	(revision 485)
+++ /trunk/nv/gfx/skeleton_instance.hh	(revision 486)
@@ -48,15 +48,30 @@
 			return clamp_frame( ( ms * 0.001f ) * float( fps ) );
 		}
+
+		inline float frame_from_ms_fps( uint32 ms, float fps ) const
+		{
+			return clamp_frame( ( ms * 0.001f ) * fps );
+		}
 	};
 
+	template < typename Transform >
 	class bone_transforms
 	{
 	public:
+		typedef Transform value_type;
+
 		bone_transforms() {}
-		void prepare( const data_node_list& bone_data );
+		void prepare( const data_node_list& bone_data )
+		{
+			m_offsets.resize( bone_data.size() );
+			for ( uint16 bi = 0; bi < bone_data.size(); ++bi )
+				m_offsets[bi] = value_type( bone_data[bi].transform );
+		}
+
 		uint32 size() const { return m_offsets.size(); };
 	protected:
-		dynamic_array< mat4 >   m_offsets;
+		dynamic_array< value_type >   m_offsets;
 
+		template < typename T >
 		friend class skeleton_instance;
 	};
@@ -76,4 +91,5 @@
 		uint32                  m_bone_count;
 
+		template < typename T >
 		friend class skeleton_instance;
 		friend class skeleton_transforms;
@@ -90,38 +106,14 @@
 		void assign( const data_node_list* node_data );
 		void assign( const skeleton_transforms& other );
+		void assign( const skeleton_transforms& other, const array_view< bool >& mask );
 
-		void interpolate_linear( const skeleton_transforms& a, const skeleton_transforms& b, float t );
-		void interpolate_nlerp( const skeleton_transforms& a, const skeleton_transforms& b, float t );
-		void interpolate_slerp( const skeleton_transforms& a, const skeleton_transforms& b, float t );
-		void interpolate4( const skeleton_transforms& s1, const skeleton_transforms& v1, const skeleton_transforms& v2, const skeleton_transforms& s2, float t );
-		void interpolate_squad( const skeleton_transforms& s1, const skeleton_transforms& v1, const skeleton_transforms& v2, const skeleton_transforms& s2, float t );
-
-// 		void animate( const mesh_nodes_data* node_data, float frame, bool local = false )
-// 		{
-// 			if ( local )
-// 			{
-// 				animate_local( node_data, frame );
-// 				return;
-// 			}
-// 			if ( m_transforms.size() != node_data->size() )
-// 				m_transforms.resize( node_data->size() );
-// 			for ( uint32 n = 0; n < node_data->size(); ++n )
-// 				if ( node_data->get_info( n ).parent_id == -1 )
-// 					animate_rec( node_data, frame, n, transform(), false );
-// 		}
-		void blend_slerp( const skeleton_transforms& a, const skeleton_transforms& b, float t, float blend );
-
-// 		void blend( const mesh_nodes_data* node_data, float frame, float blend, bool local = false )
-// 		{
-// 			NV_ASSERT( m_transforms.size() == node_data->size(), "skeleton size wrong!" );
-// 			if ( local )
-// 			{
-// 				blend_local( node_data, frame, blend );
-// 				return;
-// 			}
-// 			for ( uint32 n = 0; n < node_data->size(); ++n )
-// 				if ( node_data->get_info( n ).parent_id == -1 )
-// 					blend_rec( node_data, frame, n, transform(), false, blend );
-// 		}
+		void interpolate( const skeleton_transforms& a, const skeleton_transforms& b, float t, interpolation i );
+		void interpolate( const skeleton_transforms& a, const skeleton_transforms& b, float t, interpolation i, const array_view< bool >& mask );
+		void interpolate( const skeleton_transforms& s1, const skeleton_transforms& v1, const skeleton_transforms& v2, const skeleton_transforms& s2, float t, interpolation i );
+		void 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 );
+		void blend( const skeleton_transforms& a, const skeleton_transforms& b, float t, interpolation i, float blend, interpolation bi );
+		void blend( const skeleton_transforms& a, const skeleton_transforms& b, float t, interpolation i, float blend, interpolation bi, const array_view< bool >& mask );
+		void 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 );
+		void 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 );
 
 		void delocalize( const data_node_tree& node_data )
@@ -134,22 +126,48 @@
 		void delocalize_rec( const data_node_tree& node_data, uint32 id, const transform& parent );
 
-//		void animate_rec( const mesh_nodes_data* node_data, float frame, uint32 id, const transform& parent, bool local );
-// 		void blend_rec( const mesh_nodes_data* node_data, float frame, uint32 id, const transform& parent, bool local, float blend );
-// 		void animate_local( const mesh_nodes_data* node_data, float frame );
-// 		void blend_local( const mesh_nodes_data* node_data, float frame, float blend );
-
 //	protected:
 		dynamic_array< transform > m_transforms;
 	};
 
+	template < typename Transform >
 	class skeleton_instance
 	{
 	public:
+		typedef Transform value_type;
+
 		skeleton_instance() {}
-		const mat4* transforms() const { return m_matrix.data(); }
+		const mat4* xforms() const { return m_matrix.data(); }
 		size_t size() const { return m_matrix.size(); }
-		void assign( const skeleton_transforms& skeleton, const skeleton_binding& binding, const bone_transforms& bones );
-		void assign( const skeleton_transforms& skeleton, const bone_transforms& binding );
-		void assign( const bone_transforms& binding );
+		void assign( const skeleton_transforms& skeleton, const skeleton_binding& binding, const bone_transforms< value_type >& 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();
+					m_matrix[bone_id] = ( transforms[n] * bones.m_offsets[bone_id] ).extract();
+				}
+			}
+		}
+		void assign( const skeleton_transforms& skeleton, const bone_transforms< value_type >& 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 )
+			{
+				m_matrix[n] = ( transforms[n] * bones.m_offsets[n]  ).extract();
+			}
+		}
+
+		void assign( const bone_transforms< value_type >& bones )
+		{
+			if ( bones.size() != m_matrix.size() ) m_matrix.resize( bones.size() );
+		}
 	protected:
 
Index: /trunk/nv/image/png_loader.hh
===================================================================
--- /trunk/nv/image/png_loader.hh	(revision 485)
+++ /trunk/nv/image/png_loader.hh	(revision 486)
@@ -21,4 +21,5 @@
 		png_loader();
 		virtual bool get_info( stream&, image_format& format, ivec2& size );
+		virtual bool test( stream& );
 		virtual image_data* load( stream& );
 		virtual image_data* load( stream&, image_format format );
Index: /trunk/nv/interface/camera.hh
===================================================================
--- /trunk/nv/interface/camera.hh	(revision 485)
+++ /trunk/nv/interface/camera.hh	(revision 486)
@@ -34,8 +34,12 @@
 		{
 			m_projection = math::perspective( math::radians( fov ), aspect, near, far );
+			m_near = near;
+			m_far  = far;
 		}
 		void set_ortho( f32 left, f32 right, f32 bottom, f32 top, f32 near = -1.0f, f32 far = 1.0f )
 		{
 			m_projection = math::ortho( left, right, bottom, top, near, far );
+			m_near = near;
+			m_far  = far;
 		}
 		const mat4& get_projection() const 
@@ -55,4 +59,6 @@
 			return m_direction;
 		}
+		float get_near() const { return m_near;  }
+		float get_far() const { return m_far; }
 	private:
 		bool m_dirty;
@@ -61,4 +67,6 @@
 		mat4 m_projection;
 		mat4 m_view;
+		float m_near;
+		float m_far;
 	};
 
Index: /trunk/nv/interface/device.hh
===================================================================
--- /trunk/nv/interface/device.hh	(revision 485)
+++ /trunk/nv/interface/device.hh	(revision 486)
@@ -59,4 +59,5 @@
 		TEX_NORMAL   = 2,
 		TEX_GLOSS    = 3,
+		TEX_EMISSIVE = 4,
 		TEXTURE_0    = 0,
 		TEXTURE_1    = 1,
@@ -287,4 +288,6 @@
 		{
 			engine_uniform_factory_map& factory_map = get_uniform_factory();
+			factory_map[ "nv_f_near"] = new engine_uniform_factory< engine_uniform_f_near >();
+			factory_map[ "nv_f_far"]  = new engine_uniform_factory< engine_uniform_f_far >();
 			factory_map[ "nv_m_view" ]          = new engine_uniform_factory< engine_uniform_m_view >();
 			factory_map[ "nv_m_view_inv" ]      = new engine_uniform_factory< engine_uniform_m_view_inv >();
@@ -316,4 +319,5 @@
 			factory_link_map[ "nv_t_normal"  ] = new engine_link_uniform_int<2>();
 			factory_link_map[ "nv_t_gloss"   ] = new engine_link_uniform_int<3>();
+			factory_link_map[ "nv_t_emissive"] = new engine_link_uniform_int<4>();
 		}
 		void destroy_engine_uniforms()
Index: /trunk/nv/interface/easing.hh
===================================================================
--- /trunk/nv/interface/easing.hh	(revision 486)
+++ /trunk/nv/interface/easing.hh	(revision 486)
@@ -0,0 +1,255 @@
+// 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.
+
+/**
+ * @file easing.hh
+ * @author Kornel Kisielewicz epyon@chaosforge.org
+ * @brief Easing interface class
+ *
+ * Based on http://easings.net/
+ */
+
+
+#ifndef NV_INTERFACE_EASING_HH
+#define NV_INTERFACE_EASING_HH
+
+#include <nv/common.hh>
+#include <nv/stl/math.hh>
+
+namespace nv
+{
+
+	namespace detail
+	{
+
+		template < typename T >
+		struct easing_back_impl
+		{
+			static T eval( T t )
+			{
+				const T s( T( 1.70158 ) );
+				return t * t * ( ( s + T( 1 ) ) * t - s );
+			}
+		};
+
+		template < typename T >
+		struct easing_bounce_impl
+		{
+			static T eval( T t )
+			{
+				const T v = T( 1 ) - t;
+				T c, d;
+					 if ( v < T( 1.0 / 2.75 ) ) { c = v; d = T( 0 ); }
+				else if ( v < T( 2.0 / 2.75 ) ) { c = v - T( 1.5 / 2.75 );  d = T( 0.75 ); }
+				else if ( v < T( 2.5 / 2.75 ) ) { c = v - T( 2.25 / 2.75 ); d = T( 0.9375 ); }
+				else { c = v - T( 2.625 / 2.75 ); d = T( 0.984375 ); }
+				return T( 1 ) - ( T( 7.5625 ) * c * c + d );
+			}
+		};
+
+		template < typename T >
+		struct easing_circ_impl
+		{
+			static T eval( T t )
+			{
+				return T( 1 ) - nv::sqrt( T( 1 ) - t * t );
+			}
+		};
+		
+		template < typename T >
+		struct easing_cubic_impl
+		{
+			static T eval( T t )
+			{
+				return t * t * t;
+			}
+		};
+
+		template < typename T >
+		struct easing_elastic_impl
+		{
+			static T eval( T t )
+			{
+				const T two_pi( math::two_pi<T>() );
+				const T v( t - 1 );
+				const T p( T( 0.3 ) );
+				return -nv::pow( T(2), T(10) * v ) * nv::sin( ( v - p * T( 0.25 ) ) * two_pi / p );
+			}
+		};
+
+		template < typename T >
+		struct easing_expo_impl
+		{
+			static T eval( T t )
+			{
+				return t == T(0) ? T(0) : nv::pow( 2, T( 10 ) * ( t - T( 1 ) ) );
+			}
+		};
+
+		template < typename T >
+		struct easing_linear_impl
+		{
+			static T eval( T t )
+			{
+				return t;
+			}
+		};
+
+		template < typename T >
+		struct easing_quad_impl
+		{
+			static T eval( T t )
+			{
+				return t * t;
+			}
+		};
+
+		template < typename T >
+		struct easing_quart_impl
+		{
+			static T eval( T t )
+			{
+				const T tt = t * t;
+				return tt * tt;
+			}
+		};
+
+		template < typename T >
+		struct easing_quint_impl
+		{
+			static T eval( T t )
+			{
+				const T tt = t * t;
+				return tt * tt * t;
+			}
+		};
+
+		template < typename T >
+		struct easing_sine_impl
+		{
+			static T eval( T t )
+			{
+				const T half_pi( math::half_pi<T>() );
+				return T( 1 ) - nv::cos( t * half_pi );
+			}
+		};
+
+		template < typename T, typename Easing >
+		struct easing_impl
+		{
+			static T eval( T t )
+			{
+				return Easing::eval( t );
+			}
+
+			static T in( T t )
+			{
+				return Easing::eval( t );
+			}
+
+			static T out( T t )
+			{
+				return T( 1 ) - Easing::eval( T( 1 ) - t );
+			}
+
+			static T in_out( T t )
+			{
+				if ( t < T( 0.5 ) )
+					return Easing::eval( T( 2 ) * t ) * T( 0.5 );
+				else
+					return T( 1 ) - Easing::eval( T( 2 ) - ( T( 2 ) * t ) ) * T( 0.5 );
+			}
+		};
+
+	}
+
+	template < typename T, typename EaseIn, typename EaseOut >
+	struct easing_composite
+	{
+		static T eval( T t )
+		{
+			if ( t < T( 0.5 ) )
+				return EaseIn::eval( T( 2 ) * t ) * T( 0.5 );
+			else
+				return T( 1 ) - EaseOut::eval( T( 2 ) - T( 2 ) * t ) * T( 0.5 );
+		}
+	};
+
+	enum class easing_type
+	{
+		NONE,
+		BACK,
+		BOUNCE,
+		CIRC,
+		CUBIC,
+		ELASTIC,
+		EXPO,
+		LINEAR,
+		QUAD,
+		QUART,
+		QUINT,
+		SINE
+	};
+
+	struct easing
+	{
+		easing_type in;
+		easing_type out;
+	};
+
+	template < typename T >
+	inline T easing_eval( T t, easing_type e )
+	{
+		switch ( e )
+		{
+		case easing_type::BACK   : return detail::easing_back_impl<T>::eval( t );
+		case easing_type::BOUNCE : return detail::easing_bounce_impl<T>::eval( t );
+		case easing_type::CIRC   : return detail::easing_circ_impl<T>::eval( t );
+		case easing_type::CUBIC  : return detail::easing_cubic_impl<T>::eval( t );
+		case easing_type::ELASTIC: return detail::easing_elastic_impl<T>::eval( t );
+		case easing_type::EXPO   : return detail::easing_expo_impl<T>::eval( t );
+		case easing_type::LINEAR : return detail::easing_linear_impl<T>::eval( t );
+		case easing_type::QUAD   : return detail::easing_quad_impl<T>::eval( t );
+		case easing_type::QUART  : return detail::easing_quart_impl<T>::eval( t );
+		case easing_type::QUINT  : return detail::easing_quint_impl<T>::eval( t );
+		case easing_type::SINE   : return detail::easing_sine_impl<T>::eval( t );
+		default:
+			break;
+		}
+		return T( 0 );
+	};
+
+	template < typename T >
+	inline T easing_eval( T t, easing_type in, easing_type out )
+	{
+		if ( in != easing_type::NONE )
+		{
+			if ( out != easing_type::NONE )
+			{
+				if ( t < T( 0.5 ) )
+					return easing_eval( T( 2 ) * t, in ) * T( 0.5 );
+				else
+					return T( 1 ) - easing_eval( T( 2 ) - T( 2 ) * t, out ) * T( 0.5 );
+			}
+			else
+				return easing_eval( t, in );
+		}
+		else if ( out != easing_type::NONE )
+		{
+			return T( 1 ) - easing_eval( T( 1 ) - t, out );
+		}
+		return T( 0 );
+	}
+
+	template < typename T >
+	inline T easing_eval( T t, easing e )
+	{
+		return easing_eval( t, e.in, e.out );
+	}
+
+}
+
+#endif // NV_INTERFACE_EASING_HH
Index: /trunk/nv/interface/image_data.hh
===================================================================
--- /trunk/nv/interface/image_data.hh	(revision 485)
+++ /trunk/nv/interface/image_data.hh	(revision 486)
@@ -51,5 +51,19 @@
 	public:
 		image_data( image_format format, ivec2 size, const uint8 * data ) 
-			: m_format( format ), m_size( size ), m_data( nullptr )	{ initialize( data ); }
+			: m_format( format ), m_size( size ), m_data( nullptr )
+		{
+			NV_ASSERT_ALWAYS( data, "Data = nullptr!" );
+			initialize( data );
+		}
+		image_data( image_format format, ivec2 size )
+			: m_format( format ), m_size( size ), m_data( nullptr )
+		{
+			initialize( nullptr );
+		}
+		void fill( uint8 value = 0 )
+		{
+			size_t bsize = static_cast<size_t>( m_size.x * m_size.y ) * get_depth();
+			raw_fill_n( m_data, bsize, value );
+		}
 		uint8* release_data() { uint8* r = m_data; m_data = nullptr; return r; }
 		const uint8 * get_data()    const { return m_data; }
@@ -64,5 +78,6 @@
 			size_t bsize = static_cast<size_t>(m_size.x * m_size.y) * get_depth();
 			m_data = new uint8[ bsize ]; 
-			raw_copy( data, data + bsize, m_data );
+			if ( data )
+				raw_copy( data, data + bsize, m_data );
 		}
 
Index: /trunk/nv/interface/image_loader.hh
===================================================================
--- /trunk/nv/interface/image_loader.hh	(revision 485)
+++ /trunk/nv/interface/image_loader.hh	(revision 486)
@@ -25,4 +25,5 @@
 	public:
 		virtual bool get_info( stream&, image_format& format, ivec2& size ) = 0;
+		virtual bool test( stream& ) = 0;
 		virtual image_data* load( stream& ) = 0;
 		virtual image_data* load( stream&, image_format format ) = 0;
Index: /trunk/nv/interface/interpolate.hh
===================================================================
--- /trunk/nv/interface/interpolate.hh	(revision 486)
+++ /trunk/nv/interface/interpolate.hh	(revision 486)
@@ -0,0 +1,313 @@
+// Copyright (C) 2015-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.
+
+// WARNING: this file is explicitly designed to fuck with your brain
+
+#ifndef NV_INTERFACE_INTERPOLATE_HH
+#define NV_INTERFACE_INTERPOLATE_HH
+
+#include <nv/common.hh>
+#include <nv/core/transform.hh>
+#include <nv/stl/math.hh>
+#include <nv/interface/data_descriptor.hh>
+
+namespace nv
+{
+
+	template < typename T >
+	struct no_interpolator
+	{
+		inline static T interpolate( float, const T& lhs, const T& ) { return lhs; }
+		inline static T interpolate( float, const T& , const T& v1, const T& , const T& ) { return v1; }
+	};
+
+	template < typename T >
+	struct linear_interpolator
+	{
+		inline static T interpolate( float f, const T& lhs, const T& rhs ) { return math::lerp( lhs, rhs, f ); }
+		inline static T interpolate( float f, const T& , const T& v1, const T& v2, const T& ) { return math::lerp( v1, v2, f ); }
+	};
+
+	template < typename T >
+	struct normalized_interpolator
+	{
+		inline static T interpolate( float f, const T& lhs, const T& rhs ) { return math::lerp( lhs, rhs, f ); }
+		inline static T interpolate( float f, const T& , const T& v1, const T& v2, const T& ) { return math::lerp( v1, v2, f ); }
+	};
+
+	template <>
+	struct normalized_interpolator< quat >
+	{
+		inline static quat interpolate( float f, const quat& lhs, const quat& rhs ) { return math::nlerp( lhs, rhs, f ); }
+		inline static quat interpolate( float f, const quat& , const quat& v1, const quat& v2, const quat& ) { return math::nlerp( v1, v2, f ); }
+	};
+
+	template <>
+	struct normalized_interpolator< transform >
+	{
+		inline static transform interpolate( float f, const transform& lhs, const transform& rhs ) { return transform( math::lerp( lhs.get_position(), rhs.get_position(), f ), math::nlerp( lhs.get_orientation(), rhs.get_orientation(), f ) ); }
+		inline static transform interpolate( float f, const transform& , const transform& v1, const transform& v2, const transform& ) { return transform( math::lerp( v1.get_position(), v2.get_position(), f ), math::nlerp( v1.get_orientation(), v2.get_orientation(), f ) ); }
+	};
+
+	template < typename T >
+	struct spherical_interpolator
+	{
+		inline static T interpolate( float f, const T& lhs, const T& rhs ) { return math::lerp( lhs, rhs, f ); }
+		inline static T interpolate( float f, const T& , const T& v1, const T& v2, const T& ) { return math::lerp( v1, v2, f ); }
+	};
+
+	template <>
+	struct spherical_interpolator< quat >
+	{
+		inline static quat interpolate( float f, const quat& lhs, const quat& rhs ) { return math::slerp( lhs, rhs, f ); }
+		inline static quat interpolate( float f, const quat& , const quat& v1, const quat& v2, const quat& ) { return math::slerp( v1, v2, f ); }
+	};
+
+	template <>
+	struct spherical_interpolator< transform >
+	{
+		inline static transform interpolate( float f, const transform& lhs, const transform& rhs ) { return transform( math::lerp( lhs.get_position(), rhs.get_position(), f ), math::slerp( lhs.get_orientation(), rhs.get_orientation(), f ) ); }
+		inline static transform interpolate( float f, const transform& , const transform& v1, const transform& v2, const transform& ) { return transform( math::lerp( v1.get_position(), v2.get_position(), f ), math::slerp( v1.get_orientation(), v2.get_orientation(), f ) ); }
+	};
+
+	struct quadratic_interpolator_base
+	{
+		float weights[4];
+
+		quadratic_interpolator_base( float value ) 
+		{
+			float interp_squared = value*value;
+			float interp_cubed = interp_squared*value;
+			weights[0] = 0.5f * ( -interp_cubed + 2.0f * interp_squared - value );
+			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 + value );
+			weights[3] = 0.5f * ( interp_cubed - interp_squared );
+		}
+	};
+
+	template < typename T >
+	struct quadratic_interpolator : public quadratic_interpolator_base
+	{
+		using quadratic_interpolator_base::quadratic_interpolator_base;
+		inline static T interpolate( float f, const T& lhs, const T& rhs ) { return math::lerp( lhs, rhs, f ); }
+		inline T interpolate( float, const T& v0, const T& v1, const T& v2, const T& v3 ) const
+		{
+			return
+				weights[0] * v0 +
+				weights[1] * v1 +
+				weights[2] * v2 +
+				weights[3] * v3;
+		}
+	};
+
+	template <>
+	struct quadratic_interpolator< quat > : public quadratic_interpolator_base
+	{
+		using quadratic_interpolator_base::quadratic_interpolator_base;
+		inline static quat interpolate( float f, const quat& lhs, const quat& rhs ) { return math::lerp( lhs, rhs, f ); }
+		inline quat interpolate( float, const quat& v0, const quat& v1, const quat& v2, const quat& v3 ) const
+		{
+			float a = dot( v1, v2 ) > 0.0f ? 1.0f : -1.0f;
+			return normalize(
+				weights[0] * v0 +
+				weights[1] * ( a * v1 ) +
+				weights[2] * v2 +
+				weights[3] * v3
+				);
+		}
+	};
+
+	template <>
+	struct quadratic_interpolator< transform > : public quadratic_interpolator_base
+	{
+		using quadratic_interpolator_base::quadratic_interpolator_base;
+		inline static transform interpolate( float f, const transform& lhs, const transform& rhs ) { return math::lerp( lhs, rhs, f ); }
+		inline transform interpolate( float, const transform& v0, const transform& v1, const transform& v2, const transform& v3 ) const
+		{
+			float a = dot( v1.get_orientation(), v2.get_orientation() ) > 0.0f ? 1.0f : -1.0f;
+			return transform( 
+				weights[0] * v0.get_position() +
+				weights[1] * v1.get_position() +
+				weights[2] * v2.get_position() +
+				weights[3] * v3.get_position(),
+				normalize(
+				weights[0] * v0.get_orientation() +
+				weights[1] * ( a * v1.get_orientation() ) +
+				weights[2] * v2.get_orientation() +
+				weights[3] * v3.get_orientation()
+				) );
+		}
+	};
+
+	template < typename T >
+	struct squad_interpolator
+	{
+		inline static T interpolate( float f, const T& lhs, const T& rhs ) { return math::slerp( lhs, rhs, f ); }
+		inline static T interpolate( float f, const T& v0, const T& v1, const T& v2, const T& v3 ) { return math::slerp( v1, v2, f ); }
+	};
+
+	template <>
+	struct squad_interpolator< quat >
+	{
+		inline static quat interpolate( float f, const quat& lhs, const quat& rhs ) { return math::slerp( lhs, rhs, f ); }
+		inline static quat interpolate( float f, const quat& v0, const quat& v1, const quat& v2, const quat& v3 )
+		{
+			return normalize( math::squad(
+				v1, v2,
+				math::intermediate( v0, v1, v2 ),
+				math::intermediate( v1, v2, v3 ),
+				f ) );
+		}
+	};
+
+	template <>
+	struct squad_interpolator< transform >
+	{
+		inline static transform interpolate( float f, const transform& lhs, const transform& rhs ) { return spherical_interpolator<transform>::interpolate( f, lhs, rhs ); }
+		inline static transform interpolate( float f, const transform& v0, const transform& v1, const transform& v2, const transform& v3 )
+		{
+			return transform(
+				mix( v1.get_position(), v2.get_position(), f ),
+				squad_interpolator< quat >::interpolate( f,
+					v0.get_orientation(),
+					v1.get_orientation(),
+					v2.get_orientation(),
+					v3.get_orientation()
+					) );
+		}
+	};
+
+
+	enum class interpolation
+	{
+		NONE,
+		LINEAR,
+		NORMALIZED,
+		SPHERICAL,
+		QUADRATIC,
+		SQUADRATIC,
+	};
+
+	template < typename T, typename Interpolator, typename ...Args >
+	T interpolate( float f, const Interpolator& i, Args&&... args )
+	{
+		NV_UNUSED( i );
+		return i.interpolate( f, ::nv::forward<Args>( args )... );
+	}
+	
+	template < typename T, typename ...Args >
+	T interpolate( float f, interpolation i, Args&&... args )
+	{
+		switch ( i )
+		{
+		case interpolation::LINEAR        : return linear_interpolator<T>::interpolate( f, ::nv::forward<Args>( args )... );
+		case interpolation::NORMALIZED    : return normalized_interpolator<T>::interpolate( f, ::nv::forward<Args>( args )... );
+		case interpolation::SPHERICAL     : return spherical_interpolator<T>::interpolate( f, ::nv::forward<Args>( args )... );
+		case interpolation::QUADRATIC     : return quadratic_interpolator<T>::interpolate( f, ::nv::forward<Args>( args )... );
+		case interpolation::SQUADRATIC    : return squad_interpolator<T>::interpolate( f, ::nv::forward<Args>( args )... );
+		default: case interpolation::NONE : return no_interpolator<T>::interpolate( f, ::nv::forward<Args>( args )... );
+		}
+	}
+
+	template < typename T, typename ...Args >
+	void interpolate( T& result, float f, interpolation i, Args&&... args )
+	{
+		typedef typename T::value_type value_type;
+		switch ( i )
+		{
+		case interpolation::LINEAR        : interpolate_array( result, f, linear_interpolator<value_type>(), ::nv::forward<Args>( args )... ); break;
+		case interpolation::NORMALIZED    : interpolate_array( result, f, normalized_interpolator<value_type>(), ::nv::forward<Args>( args )... ); break;
+		case interpolation::SPHERICAL     : interpolate_array( result, f, spherical_interpolator<value_type>(), ::nv::forward<Args>( args )... ); break;
+		case interpolation::QUADRATIC     : interpolate_array( result, f, quadratic_interpolator<value_type>( f ), ::nv::forward<Args>( args )... ); break;
+		case interpolation::SQUADRATIC    : interpolate_array( result, f, squad_interpolator<value_type>(), ::nv::forward<Args>( args )... ); break;
+		default: case interpolation::NONE : interpolate_array( result, f, no_interpolator<value_type>(), ::nv::forward<Args>( args )... ); break;
+		}
+	}
+
+	template < typename T, typename Interpolator >
+	auto interpolate_extract( float f, const Interpolator& i, uint32 n, const T& a1, const T& a2 )
+		-> typename T::value_type
+	{
+		NV_UNUSED( i );
+		return i.interpolate( f, a1[n], a2[n] );
+	}
+
+	template < typename T, typename Interpolator >
+	auto interpolate_extract( float f, const Interpolator& i, uint32 n, const T& a0, const T& a1, const T& a2, const T& a3 )
+		-> typename T::value_type
+	{
+		NV_UNUSED( i );
+		return i.interpolate( f, a0[n], a1[n], a2[n], a3[n] );
+	}
+
+
+	template < typename T, typename Interpolator, typename ...Args >
+	void interpolate_array( T& result, float f, const Interpolator& in, Args&&... args )
+	{
+		uint32 size = result.size();
+		for ( uint32 n = 0; n < size; ++n )
+		{
+			result[n] = interpolate_extract( f, in, n, ::nv::forward<Args>( args )... );
+		}
+	}
+
+	template < typename T, typename Interpolator, typename ...Args >
+	void interpolate_array( T& a, float f, const Interpolator& in, const array_view< bool >& mask, Args&&... args )
+	{
+		uint32 size = a.size();
+		for ( uint32 n = 0; n < size; ++n )
+			if ( mask[ n ] )
+			{
+				a[n] = interpolate_extract( f, in, n, ::nv::forward<Args>( args )... ); 
+			}
+	}
+
+	template < typename T, typename Interpolator, typename... Args >
+	void interpolate_array( T& result, float f, const Interpolator& in, float blend_factor, interpolation bi, Args&&... args )
+	{
+		typedef typename T::value_type value_type;
+		switch ( bi )
+		{
+		case interpolation::LINEAR        : interpolate_blend( result, f, in, blend_factor, linear_interpolator<value_type>(), ::nv::forward<Args>( args )... ); break;
+		case interpolation::NORMALIZED    : interpolate_blend( result, f, in, blend_factor, normalized_interpolator<value_type>(), ::nv::forward<Args>( args )... ); break;
+		case interpolation::SPHERICAL     : interpolate_blend( result, f, in, blend_factor, spherical_interpolator<value_type>(), ::nv::forward<Args>( args )... ); break;
+		case interpolation::QUADRATIC     : interpolate_blend( result, f, in, blend_factor, normalized_interpolator<value_type>(), ::nv::forward<Args>( args )... ); break;
+		case interpolation::SQUADRATIC    : interpolate_blend( result, f, in, blend_factor, spherical_interpolator<value_type>(), ::nv::forward<Args>( args )... ); break;
+		default: case interpolation::NONE : interpolate_blend( result, f, in, blend_factor, no_interpolator<value_type>(), ::nv::forward<Args>( args )... ); break;
+		}
+	}
+
+	template < typename T, typename Interpolator, typename BlendInterpolator, typename... Args >
+	void interpolate_blend( T& a, float f, const Interpolator& in, float blend_factor, const BlendInterpolator& bin, Args&&... args )
+	{
+		NV_UNUSED( bin );
+		typedef typename T::value_type value_type;
+		uint32 size = a.size();
+		for ( uint32 n = 0; n < size; ++n )
+		{
+			value_type temp = interpolate_extract( f, in, n, ::nv::forward<Args>( args )... );
+			a[n] = bin.interpolate( blend_factor, a[n], temp );
+		}
+	}
+
+	template < typename T, typename Interpolator, typename BlendInterpolator, typename... Args >
+	void interpolate_blend( T& a, float f, const Interpolator& in, float blend_factor, const BlendInterpolator& bin, const array_view< bool >& mask, Args&&... args )
+	{
+		NV_UNUSED( bin );
+		typedef typename T::value_type value_type;
+		uint32 size = a.size();
+		for ( uint32 n = 0; n < size; ++n )
+			if ( mask[n] )
+			{
+				value_type temp = interpolate_extract( f, in, n, ::nv::forward<Args>( args )... );
+				a[n] = bin.interpolate( blend_factor, a[n], temp );
+			}
+	}
+
+
+
+}
+#endif // NV_INTERFACE_INTERPOLATE_HH
Index: /trunk/nv/interface/uniform.hh
===================================================================
--- /trunk/nv/interface/uniform.hh	(revision 485)
+++ /trunk/nv/interface/uniform.hh	(revision 486)
@@ -157,4 +157,18 @@
 	typedef nv::unordered_map< string_view, engine_link_uniform_base* >    engine_link_uniform_factory_map;
 
+	class engine_uniform_f_near : public engine_uniform< float >
+	{
+	public:
+		engine_uniform_f_near( uniform_base* u ) : engine_uniform( u ) {}
+		virtual void set( const context*, const scene_state* s ) { m_uniform->set_value( s->get_camera().get_near() ); }
+	};
+
+	class engine_uniform_f_far : public engine_uniform< float >
+	{
+	public:
+		engine_uniform_f_far( uniform_base* u ) : engine_uniform( u ) {}
+		virtual void set( const context*, const scene_state* s ) { m_uniform->set_value( s->get_camera().get_far() ); }
+	};
+
 	class engine_uniform_m_view : public engine_uniform< mat4 >
 	{
Index: /trunk/nv/lua/lua_state.hh
===================================================================
--- /trunk/nv/lua/lua_state.hh	(revision 485)
+++ /trunk/nv/lua/lua_state.hh	(revision 486)
@@ -298,5 +298,7 @@
 			bool has_field( string_view element );
 			shash64 get_string_hash_64( string_view element, uint64 defval = 0 );
+			// remove this one
 			shash64 get_string( string_view element, string_table& table, uint64 defval = 0 );
+			shash64 get_string( string_view element, string_table* table, uint64 defval = 0 );
 			const_string get_string( string_view element, string_view defval = string_view() );
 			string128 get_string128( string_view element, string_view defval = string_view() );
Index: /trunk/nv/stl/container/hash_table.hh
===================================================================
--- /trunk/nv/stl/container/hash_table.hh	(revision 485)
+++ /trunk/nv/stl/container/hash_table.hh	(revision 486)
@@ -193,5 +193,5 @@
 				m_bucket_count    = other.m_bucket_count;
 				m_element_count   = other.m_element_count;
-				m_max_load_factor = other.max_load_factor;
+				m_max_load_factor = other.m_max_load_factor;
 			}
 			return *this;
@@ -619,5 +619,5 @@
 			{
 				if ( base_type::rehash_check( 1 ) ) b = base_type::get_bucket_index( h );
-				return insert_return_type( base_type::insert( b, h, ::nv::move(key), ::nv::forward< M >( obj ) ), true );
+				return insert_return_type( base_type::insert( b, h, ::nv::move(key), ::nv::move< M >( obj ) ), true );
 			}
 			return insert_return_type( r, false );
Index: /trunk/nv/stl/hash_store.hh
===================================================================
--- /trunk/nv/stl/hash_store.hh	(revision 485)
+++ /trunk/nv/stl/hash_store.hh	(revision 486)
@@ -104,5 +104,5 @@
 		{
 			insert_return_type result = base_type::try_insert( k, nv::forward<M>( obj ) );
-			if ( !result.second ) result.first->second = obj;
+			if ( !result.second ) result.first->second = nv::move(obj);
 			return result;
 		}
Index: /trunk/nv/stl/math/basic.hh
===================================================================
--- /trunk/nv/stl/math/basic.hh	(revision 485)
+++ /trunk/nv/stl/math/basic.hh	(revision 486)
@@ -52,5 +52,5 @@
 				typename T,
 				typename Mix = T,
-				bool MixBool = is_same< value_type_t<Mix>, bool >,
+				bool MixBool = is_same< value_type_t<Mix>, bool >::value,
 				bool TIsVector = is_vec<T>::value,
 				bool MixIsVector = is_vec<Mix>::value >
@@ -274,4 +274,16 @@
 		}
 
+		template < typename T, typename enable_if< is_arithmetic<T>::value >::type* = nullptr >
+		constexpr T lerp( T a, T b, T m )
+		{
+			return a + m * ( b - a );
+		}
+
+		template < typename T, typename enable_if< is_vec<T>::value >::type* = nullptr >
+		constexpr T lerp( const T& a, const T& b, value_type_t<T> m )
+		{
+			return a + m * ( b - a );
+		}
+
 		template < typename T, typename enable_if< !is_vec<T>::value >::type* = nullptr >
 		constexpr T step( T edge, T x )
Index: /trunk/nv/stl/math/mat2.hh
===================================================================
--- /trunk/nv/stl/math/mat2.hh	(revision 485)
+++ /trunk/nv/stl/math/mat2.hh	(revision 486)
@@ -287,9 +287,5 @@
 
 		template <typename T>
-		inline typename tmat2<T>::column_type operator*
-			(
-				const tmat2<T> & m,
-				typename const tmat2<T>::row_type & v
-				)
+		inline typename tmat2<T>::column_type operator* ( const tmat2<T> & m, const typename tmat2<T>::row_type & v )
 		{
 			return tvec2<T>(
@@ -299,9 +295,5 @@
 
 		template <typename T>
-		inline typename tmat2<T>::row_type operator*
-			(
-				typename const tmat2<T>::column_type & v,
-				const tmat2<T> & m
-				)
+		inline typename tmat2<T>::row_type operator* ( const typename tmat2<T>::column_type & v, const tmat2<T> & m )
 		{
 			return tvec2<T>(
@@ -337,5 +329,5 @@
 
 		template <typename T>
-		inline typename tmat2<T>::column_type operator/( const tmat2<T> & m, typename const tmat2<T>::row_type & v )
+		inline typename tmat2<T>::column_type operator/( const tmat2<T> & m, const typename tmat2<T>::row_type & v )
 		{
 			return detail::compute_inverse<T>( m ) * v;
@@ -343,5 +335,5 @@
 
 		template <typename T>
-		inline typename tmat2<T>::row_type operator/( typename const tmat2<T>::column_type & v, const tmat2<T> & m )
+		inline typename tmat2<T>::row_type operator/( const typename tmat2<T>::column_type & v, const tmat2<T> & m )
 		{
 			return v * detail::compute_inverse<T>( m );
Index: /trunk/nv/stl/math/mat3.hh
===================================================================
--- /trunk/nv/stl/math/mat3.hh	(revision 485)
+++ /trunk/nv/stl/math/mat3.hh	(revision 486)
@@ -320,5 +320,5 @@
 
 		template <typename T>
-		inline typename tmat3<T>::column_type operator*( const tmat3<T> & m, typename const tmat3<T>::row_type & v )
+		inline typename tmat3<T>::column_type operator*( const tmat3<T> & m, const typename tmat3<T>::row_type & v )
 		{
 			return typename tmat3<T>::column_type(
@@ -329,5 +329,5 @@
 
 		template <typename T>
-		inline typename tmat3<T>::row_type operator*( typename const tmat3<T>::column_type & v, const tmat3<T> & m )
+		inline typename tmat3<T>::row_type operator*( const typename tmat3<T>::column_type & v, const tmat3<T> & m )
 		{
 			return typename tmat3<T>::row_type(
@@ -372,5 +372,5 @@
 
 		template <typename T>
-		inline typename tmat3<T>::column_type operator/( const tmat3<T> & m, typename const tmat3<T>::row_type & v )
+		inline typename tmat3<T>::column_type operator/( const tmat3<T> & m, const typename tmat3<T>::row_type & v )
 		{
 			return detail::compute_inverse<T>( m ) * v;
@@ -378,5 +378,5 @@
 
 		template <typename T>
-		inline typename tmat3<T>::row_type operator/( typename const tmat3<T>::column_type & v, const tmat3<T> & m )
+		inline typename tmat3<T>::row_type operator/( const typename tmat3<T>::column_type & v, const tmat3<T> & m )
 		{
 			return v * detail::compute_inverse<T>( m );
Index: /trunk/nv/stl/math/vec2.hh
===================================================================
--- /trunk/nv/stl/math/vec2.hh	(revision 485)
+++ /trunk/nv/stl/math/vec2.hh	(revision 486)
@@ -98,5 +98,5 @@
 			{}
 
-			inline tvec2<T> & tvec2<T>::operator+=( T scalar )
+			inline tvec2<T> & operator+=( T scalar )
 			{
 				this->x += static_cast<T>( scalar );
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 ) );
