Index: trunk/nv/core/arcball.hh
===================================================================
--- trunk/nv/core/arcball.hh	(revision 352)
+++ trunk/nv/core/arcball.hh	(revision 352)
@@ -0,0 +1,78 @@
+// Copyright (C) 2015 ChaosForge Ltd
+// http://chaosforge.org/
+//
+// This file is part of NV Libraries.
+// For conditions of distribution and use, see copyright notice in nv.hh
+
+/**
+* @file arcball.hh
+* @author Kornel Kisielewicz
+* @brief arcball utility class
+*/
+
+#ifndef NV_CORE_ARCBALL_HH
+#define NV_CORE_ARCBALL_HH
+
+#include <nv/core/common.hh>
+#include <nv/core/math.hh>
+
+namespace nv
+{
+
+	class arcball
+	{
+	public:
+		arcball()
+		{
+			m_axis  = vec3( 1.0f, 0.0f, 0.0f );
+			m_angle = 0.0f;
+		}
+		arcball( ivec2 window )
+			: m_window(window)
+		{
+			m_axis  = vec3( 1.0f, 0.0f, 0.0f );
+			m_angle = 0.0f;
+		}
+		void set_size( ivec2 window )
+		{
+			m_window = window;
+		}
+		void start_drag( ivec2 mposition )
+		{
+			m_last  = mposition;
+			m_axis  = vec3( 1.0f, 0.0f, 0.0f );
+			m_angle = 0.0f;
+		}
+		void drag( ivec2 nposition )
+		{
+			glm::vec3 va = get_arcball_vector( m_last );
+			glm::vec3 vb = get_arcball_vector( nposition );
+
+			m_angle = glm::acos( glm::min( 1.0f, glm::dot( va, vb ) ) );
+			m_axis  = glm::cross( va, vb );
+			m_last  = nposition;
+		}
+		const vec3& get_axis() const { return m_axis; }
+		f32 get_angle() const { return m_angle; }
+	private:
+		glm::vec3 get_arcball_vector( const nv::ivec2& s )
+		{
+			glm::vec3 p( 1.0*s.x / m_window.x * 2 - 1.0, 1.0*s.y / m_window.y * 2 - 1.0, 0 );
+			p.y = -p.y;
+			float sq = p.x * p.x + p.y * p.y;
+			if ( sq <= 1.0f )
+				p.z = sqrt( 1.0f - sq );
+			else
+				p = glm::normalize( p );
+			return p;
+		}
+
+		ivec2 m_window;
+		ivec2 m_last;
+		vec3  m_axis;
+		f32   m_angle;
+	};
+
+}
+
+#endif // NV_CORE_ARCBALL_HH
Index: trunk/nv/core/cstring_store.hh
===================================================================
--- trunk/nv/core/cstring_store.hh	(revision 352)
+++ trunk/nv/core/cstring_store.hh	(revision 352)
@@ -0,0 +1,146 @@
+// Copyright (C) 2014 ChaosForge Ltd
+// http://chaosforge.org/
+//
+// This file is part of NV Libraries.
+// For conditions of distribution and use, see copyright notice in nv.hh
+
+/**
+* @file cstring_store.hh
+* @author Kornel Kisielewicz
+* @brief cstring_store
+*/
+
+#ifndef NV_CORE_CSTRING_STORE_HH
+#define NV_CORE_CSTRING_STORE_HH
+
+#include <nv/core/common.hh>
+#include <nv/core/array.hh>
+#include <unordered_map>
+#include <cstring>
+#include <cstdlib>
+
+namespace nv
+{
+
+	namespace detail
+	{
+
+		inline char *cstring_duplicate( const char *s )
+		{
+			size_t length = std::strlen( s ) + 1;
+			void *result = std::malloc( length );
+			if ( result == nullptr ) return nullptr;
+			return (char *)std::memcpy( result, s, length );
+		}
+
+		struct cstring_compare
+		{
+			bool operator()( const char* lhs, const char* rhs ) const
+			{
+				return strcmp( lhs, rhs ) == 0;
+			}
+		};
+
+
+		struct cstring_hash
+		{
+			int operator()( const char* str ) const
+			{
+				int seed = 131;
+				int hash = 0;
+				while ( *str )
+				{
+					hash = ( hash * seed ) + ( *str );
+					str++;
+				}
+				return hash & ( 0x7FFFFFFF );
+			}
+		};
+	}
+
+	template < typename T >
+	using cstring_unordered_map = std::unordered_map < const char*, T, detail::cstring_hash, detail::cstring_compare > ;
+
+	struct cstring_deleter : public noncopyable
+	{
+	public:
+		cstring_deleter() {}
+
+		char* duplicate( const char* s )
+		{
+			char* result = detail::cstring_duplicate( s );
+			insert( result );
+			return result;
+		}
+
+		void insert( char * s )
+		{
+			m_array.push_back( s );
+		}
+
+		~cstring_deleter()
+		{
+			for ( auto s : m_array ) free( s );
+		}
+	private:
+		std::vector< char* > m_array;
+	};
+
+	class cstring_store : public noncopyable
+	{
+	public:
+		cstring_store() {}
+
+		bool exists( const char* cstr )
+		{
+			return m_map.find( cstr ) != std::end( m_map );
+		}
+		
+		const char* get( uint32 index )
+		{
+			return index < m_array.size() ? m_array[index] : nullptr;
+		}
+
+		uint32 resolve( const char* cstr )
+		{
+			auto it = m_map.find( cstr );
+			if ( it != std::end( m_map ) )
+			{
+				return it->second;
+			}
+			return uint32(-1);
+		}
+
+		uint32 push( const char* cstr )
+		{
+			char* duplicate = detail::cstring_duplicate( cstr );
+			m_array.push_back( duplicate );
+			uint32 index = (uint32)m_array.size() - 1;
+			m_map[duplicate] = index;
+			return index;
+		}
+
+		uint32 insert( const char* cstr )
+		{
+			auto it = m_map.find( cstr );
+			if ( it == std::end( m_map ) )
+			{
+				return push( cstr );
+			}
+			return it->second;
+		}
+
+
+
+		~cstring_store()
+		{
+			for ( auto s : m_array ) free( s );
+		}
+	private:
+		std::vector< char* >            m_array;
+		cstring_unordered_map< uint32 > m_map;
+	};
+
+}
+
+#endif // NV_CORE_STRING_STORE_HH
Index: trunk/nv/core/property_store.hh
===================================================================
--- trunk/nv/core/property_store.hh	(revision 352)
+++ trunk/nv/core/property_store.hh	(revision 352)
@@ -0,0 +1,37 @@
+// Copyright (C) 2014 ChaosForge Ltd
+// http://chaosforge.org/
+//
+// This file is part of NV Libraries.
+// For conditions of distribution and use, see copyright notice in nv.hh
+
+/**
+* @file property_store.hh
+* @author Kornel Kisielewicz
+* @brief property_store
+*/
+
+#ifndef NV_CORE_PROPERTY_STORE_HH
+#define NV_CORE_PROPERTY_STORE_HH
+
+#include <nv/core/common.hh>
+#include <nv/core/handle.hh>
+
+namespace nv
+{
+
+	class property_map
+	{
+	public:
+
+	};
+
+	template < typename HANDLE >
+	class property_store
+	{
+	public:
+		nv::packed_indexed_array< property_map, HANDLE > m_components;
+	};
+
+}
+
+#endif // NV_CORE_PROPERTY_STORE_HH
Index: trunk/nv/core/string.hh
===================================================================
--- trunk/nv/core/string.hh	(revision 351)
+++ trunk/nv/core/string.hh	(revision 352)
@@ -210,4 +210,14 @@
 	}
 
+	inline std::string extract_extension( const std::string& filename )
+	{
+		size_t lastdot = filename.find_last_of( "." );
+		std::string ext;
+		if ( std::string::npos != lastdot )
+			ext = filename.substr( lastdot + 1 );
+		std::transform( ext.begin(), ext.end(), ext.begin(), ::tolower );
+		return ext;
+	}
+
 	inline std::string remove_chars_copy( std::string s, const std::string& chars ) 
 	{
Index: trunk/nv/core/types.hh
===================================================================
--- trunk/nv/core/types.hh	(revision 352)
+++ trunk/nv/core/types.hh	(revision 352)
@@ -0,0 +1,200 @@
+// Copyright (C) 2014 Kornel Kisielewicz
+// This file is part of Nova Libraries.
+// For conditions of distribution and use, see copyright notice in nv.hh
+
+#ifndef NV_CORE_TYPES_HH
+#define NV_CORE_TYPES_HH
+
+#include <nv/core/common.hh>
+#include <nv/core/math.hh>
+#include <nv/core/cstring_store.hh>
+#include <nv/core/type_traits.hh>
+#include <typeinfo>
+#include <typeindex>
+#include <utility>
+#include <unordered_map>
+#include <vector>
+#include <string>
+#include <cstddef>
+
+namespace nv
+{
+
+	struct type_entry;
+	class type_database;
+
+	enum type_flag
+	{
+		TF_POINTER     = 0x01, //!< field is a pointer
+		TF_NOSERIALIZE = 0x02, //!< ignore during serialization
+		TF_INVISIBLE   = 0x04, //!< field invisible to API
+		TF_READONLY    = 0x08, //!< read only field
+		TF_SIMPLETYPE  = 0x10, //!< raw binary I/O possible
+		TF_OWNED       = 0x20,
+		TF_CONTAINER   = 0x40, //!< is a container
+		TF_INDEXED     = 0x80, //!< type is a index to another type
+	};
+
+	struct type_field
+	{
+		uint32                name;     //!< name of the field
+		const std::type_info* raw_type; //!< typeinfo for later retrieval of type
+		type_entry*           type;     //!< pointer to field type
+		uint32                flags;    //!< flags 
+		uint32                offset;   //!< offset into parent
+	};
+
+	struct type_enum
+	{
+		uint32 name;
+		sint32 value;
+	};
+
+	struct type_entry
+	{
+		// Function types for the constructor and destructor of registered types
+		typedef void( *constructor_func )( void* );
+		typedef void( *destructor_func )( void* );
+
+		type_database*          type_db;     //!< Parent type database
+		uint32                  name;        //!< Scoped C++ name of the type
+		constructor_func        constructor; //!< Pointers to the constructor 
+		destructor_func         destructor;  //!< Pointers to the destructor 
+		size_t                  size;        //!< Result of sizeof(type) operation
+		type_entry*             base_type;   //!< Base type
+		std::vector<type_field> field_list;  //!< Field list
+		std::vector<type_enum>  enum_list;   //!< Enum list
+		cstring_store names;
+	};
+
+	class type_creator
+	{
+	public:
+		type_creator( type_database* db, type_entry* entry )
+			: m_database( db ), m_entry( entry ) {}
+
+		template <typename TYPE>
+		type_creator base();
+
+		template< typename TOBJECT, typename TFIELD >
+		type_creator field( const char* aname, TFIELD TOBJECT::*field, typename std::enable_if< is_container<TFIELD>::value, void* >::type = nullptr );
+
+		template< typename TOBJECT, typename TFIELD >
+		type_creator field( const char* aname, TFIELD TOBJECT::*field, typename std::enable_if< !is_container<TFIELD>::value, void* >::type = nullptr );
+
+		type_creator value( const char* name, sint32 value );
+	private:
+		type_database* m_database;
+		type_entry*    m_entry;
+	};
+
+	class type_database
+	{
+	public:
+		template< typename TYPE >
+		type_creator create_type( const char* name )
+		{
+			NV_ASSERT( !m_names.exists( name ), "Type redefinition!" );
+			uint32 name_idx = m_names.push( name );
+			type_entry* i_type = new type_entry;
+			i_type->type_db = this;
+			i_type->name = name_idx;
+			i_type->size = sizeof( TYPE );
+
+			i_type->constructor = construct_object < TYPE > ;
+			i_type->destructor  = destroy_object < TYPE > ;
+			m_idx_types[typeid( TYPE )] = i_type;
+			m_type_list.push_back( i_type );
+			return type_creator( this, i_type );
+		}
+
+		type_entry* get_type( const char* name )
+		{
+			uint32 name_idx = m_names.resolve( name );
+			if ( name_idx < m_type_list.size() )
+			{
+				return m_type_list[name_idx];
+			}
+			return nullptr;
+		}
+
+		type_entry* get_type( const std::type_info& t )
+		{
+			type_info_map::iterator it = m_idx_types.find( std::type_index( t ) );
+			if ( it != m_idx_types.end() )
+			{
+				return it->second;
+			}
+			return nullptr;
+		}
+
+		const char* resolve_name( uint32 name_idx )
+		{
+			return m_names.get( name_idx );
+		}
+
+		~type_database()
+		{
+			for ( auto t : m_type_list ) delete t;
+		}
+	private:
+		typedef std::vector<type_entry*>                         type_list;
+		typedef std::unordered_map<std::type_index, type_entry*> type_info_map;
+		cstring_store m_names;
+		type_list     m_type_list;
+		type_info_map m_idx_types;
+	};
+	
+	template < typename TYPE >
+	type_creator type_creator::base()
+	{
+		m_entry->base_type = m_database->get_type( typeid( TYPE ) );
+		return *this;
+	}
+
+	template< typename TOBJECT, typename TFIELD >
+	type_creator type_creator::field( const char* aname, TFIELD TOBJECT::*field, typename std::enable_if< is_container<TFIELD>::value, void* >::type )
+	{
+		NV_ASSERT( m_entry->enum_list.empty(), "Type cannot have both enums and fields!" );
+		type_field f;
+		f.name = m_entry->names.insert( name );
+		f.raw_type = &typeid( typename std::remove_pointer<typename TFIELD::value_type>::type );
+		f.type = type_db->get_type( *( f.raw_type ) );
+		f.flags = TF_CONTAINER |
+			( std::is_pointer<typename TFIELD::value_type>::value ? TF_POINTER : 0 ) |
+			( std::is_pod<typename TFIELD::value_type>::value ? TF_SIMPLETYPE : 0 );
+		f.offset = offset_of( field );
+		m_entry->field_list.push_back( f );
+		return *this;
+	}
+
+	template< typename TOBJECT, typename TFIELD >
+	type_creator type_creator::field( const char* aname, TFIELD TOBJECT::*field, typename std::enable_if< !is_container<TFIELD>::value, void* >::type )
+	{
+		NV_ASSERT( m_entry->enum_list.empty(), "Type cannot have both enums and fields!" );
+		type_field f;
+		f.name = m_entry->names.insert( name );
+		f.raw_type = &typeid( typename std::remove_pointer<TFIELD>::type );
+		f.type = type_db->get_type( *( f.raw_type ) );
+		f.flags =
+			( std::is_pointer<TFIELD>::value ? TF_POINTER : 0 ) |
+			( std::is_pod<TFIELD>::value ? TF_SIMPLETYPE : 0 );
+		f.offset = offset_of( field );
+		m_entry->field_list.push_back( f );
+		return *this;
+	}
+
+	inline type_creator type_creator::value( const char* name, sint32 value )
+	{
+		NV_ASSERT( m_entry->field_list.empty(), "Type cannot have both enums and fields!" );
+		type_enum e;
+		e.name = m_entry->names.insert( name );
+		e.value = value;
+		m_entry->enum_list.push_back( e );
+		return *this;
+	}
+
+}
+
+
+#endif // NV_CORE_TYPES_HH
Index: trunk/nv/lib/wx.hh
===================================================================
--- trunk/nv/lib/wx.hh	(revision 351)
+++ 	(revision )
@@ -1,111 +1,0 @@
-// Copyright (C) 2014 ChaosForge Ltd 
-// http://chaosforge.org/
-//
-// This file is part of NV Libraries.
-// For conditions of distribution and use, see copyright notice in nv.hh
-//
-// NOTE: this file is header only, not to have NV depend on WX
-#ifndef NV_LIB_WX_HH
-#define NV_LIB_WX_HH
-
-#if defined( NV_COMMON_HH ) || defined( _WX_WX_H_ )
-#error For proper side-by-side usage WX header needs to be included first!
-#endif
-
-#define __GL_H__
-#include <stddef.h>
-#include <nv/lib/detail/gl_types.inc>
-#include "wx/wx.h"
-#undef near
-#undef far
-#include <nv/core/common.hh>
-#include <nv/lib/gl.hh>
-#include <nv/gl/gl_device.hh>
-#include <nv/gl/gl_window.hh>
-#include <nv/sdl/sdl_input.hh>
-#include <nv/sdl/sdl_window_manager.hh>
-#include <nv/interface/context.hh>
-#include <nv/interface/window.hh>
-
-namespace nv
-{
-	class gl_canvas : public wxWindow
-	{
-	public:
-		explicit gl_canvas( wxWindow *parent );
-		virtual void OnUpdate() = 0;
-// 		virtual void Stop() { m_update_timer->Stop(); }
-// 		virtual void Start() { m_update_timer->Start(20); }
-		virtual void Stop() 
-		{
-			Disconnect( wxEVT_IDLE, wxIdleEventHandler(gl_canvas::OnIdle) );
-			//m_update_timer->Stop();
-		}
-		virtual void Start() 
-		{ 
-			Connect( wxID_ANY, wxEVT_IDLE, wxIdleEventHandler(gl_canvas::OnIdle) );
-			//m_update_timer->Start(20);
-		}
-		void OnIdle(wxIdleEvent& evt)
-		{
-			if(m_render)
-			{
-				OnUpdate();
-				evt.RequestMore(); // render continuously, not only once on idle
-			}
-		}
-		~gl_canvas();
-	protected:
-		void OnPaint( wxPaintEvent& event );
-
-		nv::window_manager* m_wm;
-		nv::device*         m_device;
-		nv::context*        m_context;
-		nv::window*         m_window;
-		//wxTimer*     m_update_timer;
-		bool                m_render;
-		wxDECLARE_EVENT_TABLE();
-	};
-
-	class gl_update_timer : public wxTimer
-	{
-	public:
-		explicit gl_update_timer( gl_canvas* canvas )
-			: wxTimer(), m_canvas( canvas ) {}
-		virtual void Notify() { m_canvas->OnUpdate(); }
-	private:
-		gl_canvas* m_canvas;
-	};
-
-// 	wxBEGIN_EVENT_TABLE(gl_canvas, wxWindow)
-// 		EVT_PAINT(gl_canvas::OnPaint)
-// 	wxEND_EVENT_TABLE()
-
-	inline gl_canvas::gl_canvas( wxWindow *parent )
-		: wxWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize,
-	wxFULL_REPAINT_ON_RESIZE)
-	{
-		nv::load_gl_no_context();
-		wxClientDC dc(this);
-		m_wm     = new nv::sdl::window_manager;
-		m_device = new nv::gl_device();
-		m_window  = new nv::gl_window( m_device, m_wm, new nv::sdl::input(), GetHWND(), dc.GetHDC() );
-		m_context = m_window->get_context();
-	}
-
-	inline gl_canvas::~gl_canvas()
-	{
-		delete m_window;
-		delete m_device;
-	}
-
-	inline void gl_canvas::OnPaint( wxPaintEvent& )
-	{
-		const wxSize client_size = GetClientSize();
-		m_context->set_viewport( nv::ivec4( nv::ivec2(), client_size.x, client_size.y ) );
-		OnUpdate();
-	}
-
-}
-
-#endif NV_LIB_WX_HH
Index: trunk/nv/rocket/rocket_interface.hh
===================================================================
--- trunk/nv/rocket/rocket_interface.hh	(revision 352)
+++ trunk/nv/rocket/rocket_interface.hh	(revision 352)
@@ -0,0 +1,25 @@
+// Copyright (C) 2012-2015 ChaosForge Ltd
+// http://chaosforge.org/
+//
+// This file is part of NV Libraries.
+// For conditions of distribution and use, see copyright notice in nv.hh
+/**
+* @file rocket_interface.hh
+* @author Kornel Kisielewicz epyon@chaosforge.org
+* @brief libRocket interface
+*/
+
+#ifndef NV_ROCKET_INTERFACE_HH
+#define NV_ROCKET_INTERFACE_HH
+
+#include <nv/core/common.hh>
+#include <nv/interface/window.hh>
+
+namespace nv
+{
+	bool rocket_initialize( nv::window* w, const char* assets_root );
+	bool rocket_event( void* context, io_event& event );
+	void rocket_shutdown();
+}
+
+#endif // NV_ROCKET_INTERFACE_HH
Index: trunk/nv/wx/wx.hh
===================================================================
--- trunk/nv/wx/wx.hh	(revision 352)
+++ trunk/nv/wx/wx.hh	(revision 352)
@@ -0,0 +1,21 @@
+// Copyright (C) 2014-2015 ChaosForge Ltd 
+// http://chaosforge.org/
+//
+// This file is part of NV Libraries.
+// For conditions of distribution and use, see copyright notice in nv.hh
+#ifndef NV_WX_HH
+#define NV_WX_HH
+
+#if defined( NV_COMMON_HH ) || defined( _WX_WX_H_ )
+#error For proper side-by-side usage WX header needs to be included first!
+#endif
+
+#define __GL_H__
+#include <stddef.h>
+#include <nv/lib/detail/gl_types.inc>
+#include "wx/wx.h"
+#undef near
+#undef far
+#include <nv/core/common.hh>
+#include <nv/lib/gl.hh>
+#endif NV_WX_HH
Index: trunk/nv/wx/wx_canvas.hh
===================================================================
--- trunk/nv/wx/wx_canvas.hh	(revision 352)
+++ trunk/nv/wx/wx_canvas.hh	(revision 352)
@@ -0,0 +1,97 @@
+// Copyright (C) 2014-2015 ChaosForge Ltd 
+// http://chaosforge.org/
+//
+// This file is part of NV Libraries.
+// For conditions of distribution and use, see copyright notice in nv.hh
+
+#ifndef NV_WX_CANVAS_HH
+#define NV_WX_CANVAS_HH
+
+#include <nv/wx/wx.hh>
+#include <nv/core/common.hh>
+#include <nv/core/logger.hh>
+#include <nv/lib/gl.hh>
+#include <nv/gl/gl_device.hh>
+#include <nv/gl/gl_window.hh>
+#include <nv/sdl/sdl_input.hh>
+#include <nv/sdl/sdl_window_manager.hh>
+#include <nv/interface/context.hh>
+#include <nv/interface/window.hh>
+
+#define NV_WX_APP_FRAME( frame ) IMPLEMENT_APP( nv::wx_app< frame > ) 
+
+namespace nv
+{
+
+	class wx_app_base : public wxApp
+	{
+	public:
+		wx_app_base();
+		virtual bool OnInit();
+		virtual int OnExit();
+		virtual bool initialize() = 0;
+	};
+
+	template< class MainFrame >
+	class wx_app : public wx_app_base
+	{
+	public:
+		wx_app() : wx_app_base() {}
+		virtual bool initialize()
+		{
+			new MainFrame();
+			return true;
+		}
+	};
+
+	class wx_gl_canvas : public wxWindow
+	{
+	public:
+		explicit wx_gl_canvas( wxWindow *parent );
+		virtual void on_update() = 0;
+		virtual void stop();
+		virtual void start();
+		virtual void on_idle( wxIdleEvent& evt );
+		~wx_gl_canvas();
+	protected:
+		virtual void on_paint( wxPaintEvent& event );
+
+		nv::window_manager* m_wm;
+		nv::device*         m_device;
+		nv::context*        m_context;
+		nv::window*         m_window;
+		bool                m_render;
+		wxDECLARE_EVENT_TABLE();
+	};
+
+	class wx_gl_canvas_lock
+	{
+	public:
+		wx_gl_canvas_lock( wx_gl_canvas* canvas ) : m_canvas( canvas ) { m_canvas->stop(); }
+		~wx_gl_canvas_lock() { m_canvas->start(); }
+	private:
+		wx_gl_canvas* m_canvas;
+	};
+
+	class wx_gl_update_timer : public wxTimer
+	{
+	public:
+		explicit wx_gl_update_timer( wx_gl_canvas* canvas )
+			: wxTimer(), m_canvas( canvas ) {}
+		virtual void Notify() { m_canvas->on_update(); }
+	private:
+		wx_gl_canvas* m_canvas;
+	};
+
+	class wx_log_text_ctrl_sink : public nv::log_sink
+	{
+	public:
+		wx_log_text_ctrl_sink( wxTextCtrl* a_text_ctrl );
+		virtual void log( nv::log_level level, const std::string& message );
+	private:
+		wxTextCtrl* m_text_ctrl;
+	};
+
+}
+
+#endif NV_WX_CANVAS_HH
Index: trunk/nv_rocket.lua
===================================================================
--- trunk/nv_rocket.lua	(revision 352)
+++ trunk/nv_rocket.lua	(revision 352)
@@ -0,0 +1,9 @@
+project "nv-rocket"
+	location (_ACTION)
+	language "C++"
+	kind "StaticLib"
+	includedirs { "." }
+	libdirs { "c:/libRocket/bin" }
+	includedirs { "c:/libRocket/Include" }	
+	files { "nv/rocket/**.hh",     "src/rocket/**.cc" }
+	links { "nv-core", "nv-lib", "nv-io", "nv-gfx", "nv-lua", "RocketCore", "RocketDebugger" }
Index: trunk/nv_wx.lua
===================================================================
--- trunk/nv_wx.lua	(revision 352)
+++ trunk/nv_wx.lua	(revision 352)
@@ -0,0 +1,16 @@
+project "nv-wx"
+	language "C++"
+	kind "StaticLib"
+	files { "nv/wx/**.hh", "src/wx/**.cc" }
+	includedirs { 
+		"../nv"
+	}
+	links { "nv-core", "nv-gl", "nv-formats", "nv-lua", "nv-lib", "nv-io", "nv-gfx", "nv-sdl" }
+
+solution "*"
+	includedirs { 
+		"C:/wxwidgets/include/msvc/",
+		"C:/wxwidgets/include/",
+	}
+	libdirs { "C:/wxwidgets/lib/vc120_dll" }
+	defines { "__WXMSW__", "_UNICODE", "WXUSINGDLL", "wxMSVC_VERSION_AUTO" }
Index: trunk/premake5.lua
===================================================================
--- trunk/premake5.lua	(revision 352)
+++ trunk/premake5.lua	(revision 352)
@@ -0,0 +1,27 @@
+solution "nv"
+	configurations { "debug", "release" }
+	targetdir "bin"
+	flags { "ExtraWarnings", "NoPCH" }
+	language "C++"
+
+   	configuration "debug"
+		defines { "DEBUG" }
+		flags { "Symbols" }
+		targetdir "bin"
+		objdir (_ACTION or "".."/debug")
+
+	configuration "release"
+		defines { "NDEBUG" }
+		flags { "Optimize" }
+		targetdir "bin"
+		objdir (_ACTION or "".."/release")
+
+	dofile("nv.lua")
+	
+newaction {
+	trigger     = "doc",
+	description = "Run doxygen",
+	execute     = function ()
+		os.execute("doxygen")
+	end
+}
Index: trunk/src/formats/md3_loader.cc
===================================================================
--- trunk/src/formats/md3_loader.cc	(revision 351)
+++ trunk/src/formats/md3_loader.cc	(revision 352)
@@ -204,6 +204,9 @@
 	source.read( md3->frames, sizeof( md3_frame_t ), static_cast<size_t>( md3->header.num_frames ) );
 
-	source.seek( md3->header.ofs_tags, origin::SET );
-	source.read( md3->tags, sizeof( md3_tag_t ), static_cast<size_t>( md3->header.num_tags * md3->header.num_frames ) );
+	if ( md3->header.num_tags > 0 )
+	{
+		source.seek( md3->header.ofs_tags, origin::SET );
+		source.read( md3->tags, sizeof( md3_tag_t ), static_cast<size_t>( md3->header.num_tags * md3->header.num_frames ) );
+	}
 
 	source.seek( md3->header.ofs_surfaces, origin::SET );
Index: trunk/src/rocket/rocket_interface.cc
===================================================================
--- trunk/src/rocket/rocket_interface.cc	(revision 352)
+++ trunk/src/rocket/rocket_interface.cc	(revision 352)
@@ -0,0 +1,499 @@
+#include "nv/rocket/rocket_interface.hh"
+
+#include <nv/interface/context.hh>
+#include <nv/gl/gl_device.hh>
+#include <nv/core/time.hh>
+#include <Rocket/Core.h>
+#include <Rocket/Debugger/Debugger.h>
+
+
+class rocket_c_file_interface : public Rocket::Core::FileInterface
+{
+public:
+	rocket_c_file_interface( const Rocket::Core::String& root ) : root( root ) {}
+	virtual ~rocket_c_file_interface() {}
+	virtual Rocket::Core::FileHandle Open( const Rocket::Core::String& path );
+	virtual void Close( Rocket::Core::FileHandle file );
+	virtual size_t Read( void* buffer, size_t size, Rocket::Core::FileHandle file );
+	virtual bool Seek( Rocket::Core::FileHandle file, long offset, int origin );
+	virtual size_t Tell( Rocket::Core::FileHandle file );
+private:
+	Rocket::Core::String root;
+};
+
+class rocket_sdl2_system_interface : public Rocket::Core::SystemInterface
+{
+public:
+	float GetElapsedTime();
+	bool LogMessage( Rocket::Core::Log::Type type, const Rocket::Core::String& message );
+};
+
+class rocket_sdl2_render_interface : public Rocket::Core::RenderInterface
+{
+public:
+	rocket_sdl2_render_interface( nv::context* context );
+
+	/// Called by Rocket when it wants to render geometry that it does not wish to optimise.
+	virtual void RenderGeometry( Rocket::Core::Vertex* vertices, int num_vertices, int* indices, int num_indices, Rocket::Core::TextureHandle texture, const Rocket::Core::Vector2f& translation );
+	/// Called by Rocket when it wants to enable or disable scissoring to clip content.
+	virtual void EnableScissorRegion( bool enable );
+	/// Called by Rocket when it wants to change the scissor region.
+	virtual void SetScissorRegion( int x, int y, int width, int height );
+
+	/// Called by Rocket when a texture is required by the library.
+	virtual bool LoadTexture( Rocket::Core::TextureHandle& texture_handle, Rocket::Core::Vector2i& texture_dimensions, const Rocket::Core::String& source );
+	/// Called by Rocket when a texture is required to be built from an internally-generated sequence of pixels.
+	virtual bool GenerateTexture( Rocket::Core::TextureHandle& texture_handle, const Rocket::Core::byte* source, const Rocket::Core::Vector2i& source_dimensions );
+	/// Called by Rocket when a loaded texture is no longer required.
+	virtual void ReleaseTexture( Rocket::Core::TextureHandle texture_handle );
+private:
+	nv::device*      m_device;
+	nv::context*     m_context;
+	nv::program      m_program;
+	nv::scene_state  m_state;
+	nv::render_state m_rstate;
+};
+
+
+static const char *rocket_vertex_shader = R"(
+#version 120
+attribute vec2 nv_position;
+attribute vec2 nv_texcoord;
+attribute vec4 nv_color;
+varying vec4 v_color;
+varying vec2 v_texcoord;
+uniform mat4 nv_m_projection;
+uniform mat4 nv_m_model;
+uniform vec2 texsize;
+void main(void)
+{
+	gl_Position = nv_m_projection * nv_m_model * vec4(nv_position.x, nv_position.y, 0.0, 1.0);
+	v_texcoord  = nv_texcoord; // / texsize;
+	v_color     = nv_color / 256;
+}
+)";
+
+static const char *rocket_fragment_shader = R"(
+#version 120
+varying vec4 v_color;
+varying vec2 v_texcoord;
+uniform sampler2D nv_t_diffuse;
+void main(void)
+{
+	vec4 tex_color = texture2D( nv_t_diffuse, v_texcoord );
+
+	gl_FragColor   = tex_color * v_color;
+}
+)";
+
+Rocket::Core::FileHandle rocket_c_file_interface::Open( const Rocket::Core::String& path )
+{
+	// Attempt to open the file relative to the application's root.
+	FILE* fp = fopen( ( root + path ).CString(), "rb" );
+	if ( fp != NULL )
+		return ( Rocket::Core::FileHandle ) fp;
+
+	// Attempt to open the file relative to the current working directory.
+	fp = fopen( path.CString(), "rb" );
+	return ( Rocket::Core::FileHandle ) fp;
+}
+
+void rocket_c_file_interface::Close( Rocket::Core::FileHandle file )
+{
+	fclose( (FILE*)file );
+}
+
+size_t rocket_c_file_interface::Read( void* buffer, size_t size, Rocket::Core::FileHandle file )
+{
+	return fread( buffer, 1, size, (FILE*)file );
+}
+
+bool rocket_c_file_interface::Seek( Rocket::Core::FileHandle file, long offset, int origin )
+{
+	return fseek( (FILE*)file, offset, origin ) == 0;
+}
+
+size_t rocket_c_file_interface::Tell( Rocket::Core::FileHandle file )
+{
+	return ftell( (FILE*)file );
+}
+
+float rocket_sdl2_system_interface::GetElapsedTime()
+{
+	return nv::get_ticks() / 1000;
+}
+
+bool rocket_sdl2_system_interface::LogMessage( Rocket::Core::Log::Type type, const Rocket::Core::String& message )
+{
+	switch ( type )
+	{
+	case Rocket::Core::Log::LT_ALWAYS: NV_LOG( nv::LOG_NOTICE, message.CString() ); break;
+	case Rocket::Core::Log::LT_ERROR: NV_LOG( nv::LOG_ERROR, message.CString() ); break;
+	case Rocket::Core::Log::LT_ASSERT: NV_LOG( nv::LOG_CRITICAL, message.CString() ); break;
+	case Rocket::Core::Log::LT_WARNING: NV_LOG( nv::LOG_WARNING, message.CString() ); break;
+	case Rocket::Core::Log::LT_INFO: NV_LOG( nv::LOG_INFO, message.CString() ); break;
+	case Rocket::Core::Log::LT_DEBUG: NV_LOG( nv::LOG_DEBUG, message.CString() ); break;
+	case Rocket::Core::Log::LT_MAX: break;
+	};
+
+	return true;
+}
+
+struct rocket_vertex
+{
+	nv::vec2   position;
+	nv::u8vec4 color;
+	nv::vec2   texcoord;
+};
+
+
+union texture_handle_convert
+{
+	Rocket::Core::TextureHandle th;
+	struct
+	{
+		nv::uint16 index;
+		nv::uint16 counter;
+	};
+};
+
+rocket_sdl2_render_interface::rocket_sdl2_render_interface( nv::context* context )
+{
+	m_context = context;
+	m_device = m_context->get_device();
+	m_rstate.depth_test.enabled = false;
+	m_rstate.culling.enabled = false;
+	m_rstate.blending.enabled = true;
+	m_rstate.blending.src_rgb_factor = nv::blending::SRC_ALPHA;
+	m_rstate.blending.dst_rgb_factor = nv::blending::ONE_MINUS_SRC_ALPHA;
+	m_rstate.blending.src_alpha_factor = nv::blending::SRC_ALPHA;
+	m_rstate.blending.dst_alpha_factor = nv::blending::ONE_MINUS_SRC_ALPHA;
+
+	m_program = m_device->create_program( rocket_vertex_shader, rocket_fragment_shader );
+
+	m_state.get_camera().set_ortho( 0.0f, float( 1024 ), float( 728 ), 0.0f );
+}
+
+void rocket_sdl2_render_interface::EnableScissorRegion( bool enable )
+{
+	m_rstate.scissor_test.enabled = enable;
+}
+
+void rocket_sdl2_render_interface::SetScissorRegion( int x, int y, int width, int height )
+{
+	m_rstate.scissor_test.pos.x = x;
+	m_rstate.scissor_test.pos.y = y;
+	m_rstate.scissor_test.dim.x = width;
+	m_rstate.scissor_test.pos.y = height;
+}
+
+bool rocket_sdl2_render_interface::LoadTexture( Rocket::Core::TextureHandle& texture_handle, Rocket::Core::Vector2i& texture_dimensions, const Rocket::Core::String& source )
+{
+	Rocket::Core::FileInterface* file_interface = Rocket::Core::GetFileInterface();
+	Rocket::Core::FileHandle file_handle = file_interface->Open( source );
+	if ( !file_handle )
+		return false;
+
+	file_interface->Seek( file_handle, 0, SEEK_END );
+	size_t buffer_size = file_interface->Tell( file_handle );
+	file_interface->Seek( file_handle, 0, SEEK_SET );
+
+	nv::uint8* buffer = new nv::uint8[buffer_size];
+	file_interface->Read( buffer, buffer_size, file_handle );
+	file_interface->Close( file_handle );
+
+	Rocket::Core::String ext = source.Substring( source.Length() - 3, 3 ).ToLower();
+
+	nv::sampler sampler( nv::sampler::LINEAR, nv::sampler::REPEAT );
+	nv::image_data* data = m_device->create_image_data( buffer, buffer_size );
+	nv::image_format iformat( nv::RGBA, nv::UBYTE );
+	if ( ext == "tga" ) iformat.format = nv::BGRA;
+
+	nv::texture tex = m_device->create_texture( data->get_size(), iformat, sampler, (void*)data->get_data() );
+	texture_dimensions.x = data->get_size().x;
+	texture_dimensions.y = data->get_size().y;
+	delete data;
+
+	typedef nv::handle_operator< nv::texture > op;
+	texture_handle_convert thc;
+	thc.index = op::get_index( tex );
+	thc.counter = op::get_counter( tex );
+	texture_handle = thc.th;
+	NV_LOG( nv::LOG_DEBUG, "load " << source.CString() << " as " << thc.index << "," << thc.counter );
+	return true;
+}
+
+bool rocket_sdl2_render_interface::GenerateTexture( Rocket::Core::TextureHandle& texture_handle, const Rocket::Core::byte* source, const Rocket::Core::Vector2i& source_dimensions )
+{
+	nv::sampler sampler( nv::sampler::LINEAR, nv::sampler::REPEAT );
+	nv::texture tex = m_device->create_texture( nv::ivec2( source_dimensions.x, source_dimensions.y ), nv::image_format( nv::RGBA, nv::UBYTE ), sampler, (void*)source );
+	typedef nv::handle_operator< nv::texture > op;
+	texture_handle_convert thc;
+	thc.index = op::get_index( tex );
+	thc.counter = op::get_counter( tex );
+	texture_handle = thc.th;
+	return true;
+}
+
+void rocket_sdl2_render_interface::ReleaseTexture( Rocket::Core::TextureHandle texture_handle )
+{
+	texture_handle_convert thc;
+	thc.th = texture_handle;
+	typedef nv::handle_operator< nv::texture > op;
+	m_device->release( op::create( thc.index, thc.counter ) );
+}
+
+
+void rocket_sdl2_render_interface::RenderGeometry( Rocket::Core::Vertex* vertices, int num_vertices, int* indices, int num_indices, Rocket::Core::TextureHandle texture, const Rocket::Core::Vector2f& translation )
+{
+	const nv::texture_info* info = nullptr;
+	if ( texture != 0 )
+	{
+		texture_handle_convert thc;
+		thc.th = texture;
+		typedef nv::handle_operator< nv::texture > op;
+		nv::texture tex( op::create( thc.index, thc.counter ) );
+		m_context->bind( tex, nv::TEX_DIFFUSE );
+		info = m_device->get_texture_info( tex );
+	}
+	else
+	{
+		m_context->bind( nv::texture(), nv::TEX_DIFFUSE );
+		return;
+	}
+
+	rocket_vertex* rv = (rocket_vertex*)vertices;
+
+	nv::vertex_array m_va = m_context->create_vertex_array( rv, num_vertices, (unsigned*)indices, num_indices, nv::STATIC_DRAW );
+	//if ( info )	m_device->set_uniform( m_program, "texsize", nv::vec2( info->size ) );
+	m_state.set_model( glm::translate( glm::mat4(), nv::vec3( translation.x, translation.y, 0.0f ) ) );
+	m_context->draw( nv::TRIANGLES, m_rstate, m_state, m_program, m_va, num_indices );
+	m_context->release( m_va );
+
+}
+
+/*
+int RocketSDL2SystemInterface::TranslateMouseButton( Uint8 button )
+{
+	switch ( button )
+	{
+	case SDL_BUTTON_LEFT:
+		return 0;
+	case SDL_BUTTON_RIGHT:
+		return 1;
+	case SDL_BUTTON_MIDDLE:
+		return 2;
+	default:
+		return 3;
+	}
+}
+
+int RocketSDL2SystemInterface::GetKeyModifiers()
+{
+	SDL_Keymod sdlMods = SDL_GetModState();
+
+	int retval = 0;
+
+	if ( sdlMods & KMOD_CTRL )
+		retval |= Rocket::Core::Input::KM_CTRL;
+
+	if ( sdlMods & KMOD_SHIFT )
+		retval |= Rocket::Core::Input::KM_SHIFT;
+
+	if ( sdlMods & KMOD_ALT )
+		retval |= Rocket::Core::Input::KM_ALT;
+
+	return retval;
+}
+
+*/
+
+int rocket_get_key_modifiers()
+{
+	return 0;
+}
+
+int rocket_get_mouse_buttons( const nv::mouse_button_event& button )
+{
+	return 0;
+}
+
+Rocket::Core::Input::KeyIdentifier rocket_translate_key( const nv::key_event& key )
+{
+	using namespace Rocket::Core::Input;
+
+	switch ( key.code )
+	{
+	case nv::KEY_NONE  : return KI_UNKNOWN;
+	case nv::KEY_SPACE : return KI_SPACE;
+	case nv::KEY_0: return KI_0;
+	case nv::KEY_1: return KI_1;
+	case nv::KEY_2: return KI_2;
+	case nv::KEY_3: return KI_3;
+	case nv::KEY_4: return KI_4;
+	case nv::KEY_5: return KI_5;
+	case nv::KEY_6: return KI_6;
+	case nv::KEY_7: return KI_7;
+	case nv::KEY_8: return KI_8;
+	case nv::KEY_9: return KI_9;
+	case nv::KEY_A: return KI_A;
+	case nv::KEY_B: return KI_B;
+	case nv::KEY_C: return KI_C;
+	case nv::KEY_D: return KI_D;
+	case nv::KEY_E: return KI_E;
+	case nv::KEY_F: return KI_F;
+	case nv::KEY_G: return KI_G;
+	case nv::KEY_H: return KI_H;
+	case nv::KEY_I: return KI_I;
+	case nv::KEY_J: return KI_J;
+	case nv::KEY_K: return KI_K;
+	case nv::KEY_L: return KI_L;
+	case nv::KEY_M: return KI_M;
+	case nv::KEY_N: return KI_N;
+	case nv::KEY_O: return KI_O;
+	case nv::KEY_P: return KI_P;
+	case nv::KEY_Q: return KI_Q;
+	case nv::KEY_R: return KI_R;
+	case nv::KEY_S: return KI_S;
+	case nv::KEY_T: return KI_T;
+	case nv::KEY_U: return KI_U;
+	case nv::KEY_V: return KI_V;
+	case nv::KEY_W: return KI_W;
+	case nv::KEY_X: return KI_X;
+	case nv::KEY_Y: return KI_Y;
+	case nv::KEY_Z: return KI_Z;
+	case nv::KEY_SCOLON:return KI_OEM_1;
+//	case nv::KEY_PLUS:return KI_OEM_PLUS;
+	case nv::KEY_COMMA:return KI_OEM_COMMA;
+	case nv::KEY_MINUS:return KI_OEM_MINUS;
+	case nv::KEY_PERIOD:return KI_OEM_PERIOD;
+	case nv::KEY_SLASH:return KI_OEM_2;
+	case nv::KEY_BQUOTE:return KI_OEM_3;
+	case nv::KEY_LBRACKET:return KI_OEM_4;
+	case nv::KEY_BSLASH:return KI_OEM_5;
+	case nv::KEY_RBRACKET:return KI_OEM_6;
+//	case nv::KEY_QUOTEDBL:return KI_OEM_7;
+// 	case nv::KEY_KP_0:return KI_NUMPAD0;
+// 	case nv::KEY_KP_1:return KI_NUMPAD1;
+// 	case nv::KEY_KP_2:return KI_NUMPAD2;
+// 	case nv::KEY_KP_3:return KI_NUMPAD3;
+// 	case nv::KEY_KP_4:return KI_NUMPAD4;
+// 	case nv::KEY_KP_5:return KI_NUMPAD5;
+// 	case nv::KEY_KP_6:return KI_NUMPAD6;
+// 	case nv::KEY_KP_7:return KI_NUMPAD7;
+// 	case nv::KEY_KP_8:return KI_NUMPAD8;
+// 	case nv::KEY_KP_9:return KI_NUMPAD9;
+// 	case nv::KEY_KP_ENTER:return KI_NUMPADENTER;
+// 	case nv::KEY_KP_MULTIPLY:return KI_MULTIPLY;
+// 	case nv::KEY_KP_PLUS:return KI_ADD;
+//	case nv::KEY_KP_MINUS: return KI_SUBTRACT;
+//	case nv::KEY_KP_PERIOD: return KI_DECIMAL;
+//	case nv::KEY_KP_DIVIDE: return KI_DIVIDE;
+//	case nv::KEY_KP_EQUALS: return KI_OEM_NEC_EQUAL;
+	case nv::KEY_BACK    : return KI_BACK;
+	case nv::KEY_TAB     : return KI_TAB;
+//	case nv::KEY_CLEAR   : return KI_CLEAR;
+	case nv::KEY_ENTER   : return KI_RETURN;
+//	case nv::KEY_PAUSE   : return KI_PAUSE;
+//	case nv::KEY_CAPSLOCK: return KI_CAPITAL;
+	case nv::KEY_PGUP  : return KI_PRIOR;
+	case nv::KEY_PGDOWN: return KI_NEXT;
+	case nv::KEY_END   : return KI_END;
+	case nv::KEY_HOME  : return KI_HOME;
+	case nv::KEY_LEFT  : return KI_LEFT;
+	case nv::KEY_UP    : return KI_UP;
+	case nv::KEY_RIGHT : return KI_RIGHT;
+	case nv::KEY_DOWN  : return KI_DOWN;
+	case nv::KEY_INSERT: return KI_INSERT;
+	case nv::KEY_DELETE: return KI_DELETE;
+	case nv::KEY_F1    : return KI_F1;
+	case nv::KEY_F2    : return KI_F2;
+	case nv::KEY_F3    : return KI_F3;
+	case nv::KEY_F4    : return KI_F4;
+	case nv::KEY_F5    : return KI_F5;
+	case nv::KEY_F6    : return KI_F6;
+	case nv::KEY_F7    : return KI_F7;
+	case nv::KEY_F8    : return KI_F8;
+	case nv::KEY_F9    : return KI_F9;
+	case nv::KEY_F10   : return KI_F10;
+	case nv::KEY_F11   : return KI_F11;
+	case nv::KEY_F12   : return KI_F12;
+// 	case nv::KEY_F13 : return KI_F13;
+// 	case nv::KEY_F14 : return KI_F14;
+// 	case nv::KEY_F15 : return KI_F15;
+// 	case nv::KEY_NUMLOCKCLEAR : return KI_NUMLOCK;
+// 	case nv::KEY_SCROLLLOCK : return KI_SCROLL;
+// 	case nv::KEY_LSHIFT  : return KI_LSHIFT;
+// 	case nv::KEY_RSHIFT  : return KI_RSHIFT;
+// 	case nv::KEY_LCTRL   : return KI_LCONTROL;
+// 	case nv::KEY_RCTRL   : return KI_RCONTROL;
+// 	case nv::KEY_LALT    : return KI_LMENU;
+// 	case nv::KEY_RALT    : return KI_RMENU;
+// 	case nv::KEY_LGUI    : return KI_LMETA;
+// 	case nv::KEY_RGUI    : return KI_RMETA;
+// 	case nv::KEY_LSUPER  : return KI_LWIN;
+//  case nv::KEY_RSUPER  : return KI_RWIN;
+	default: return KI_UNKNOWN;
+	}
+
+	return KI_UNKNOWN;
+}
+
+static rocket_sdl2_render_interface* s_render_interface = nullptr;
+static rocket_sdl2_system_interface* s_system_interface = nullptr;
+static rocket_c_file_interface*      s_file_interface   = nullptr;
+
+bool nv::rocket_event( void* context, nv::io_event& event )
+{
+	Rocket::Core::Context *Context = ( Rocket::Core::Context * )context;
+	switch ( event.type )
+	{
+	case nv::EV_MOUSE_MOVE :
+		Context->ProcessMouseMove( event.mmove.x, event.mmove.y, rocket_get_key_modifiers() );
+		break;
+	case nv::EV_MOUSE_BUTTON :
+		if ( event.mbutton.pressed )
+			Context->ProcessMouseButtonDown( rocket_get_mouse_buttons( event.mbutton ), rocket_get_key_modifiers() );
+		else
+			Context->ProcessMouseButtonUp( rocket_get_mouse_buttons( event.mbutton ), rocket_get_key_modifiers() );
+		break;
+	case nv::EV_MOUSE_WHEEL:
+		return Context->ProcessMouseWheel( event.mwheel.y, rocket_get_key_modifiers() );
+	case nv::EV_KEY:
+		if (event.key.pressed)
+		{
+			if ( event.key.code == KEY_BQUOTE )
+			{
+				Rocket::Debugger::SetVisible( !Rocket::Debugger::IsVisible() );
+				return true;
+			}
+
+			return Context->ProcessKeyDown( rocket_translate_key( event.key ) , rocket_get_key_modifiers() );
+		}
+		break;
+	default:
+		break;
+	}
+	return false;
+}
+
+bool nv::rocket_initialize( nv::window* w, const char* assets_root )
+{
+	s_render_interface = new rocket_sdl2_render_interface( w->get_context() );
+	s_system_interface = new rocket_sdl2_system_interface();
+	s_file_interface   = new rocket_c_file_interface( assets_root );
+
+	Rocket::Core::SetFileInterface( s_file_interface );
+	Rocket::Core::SetRenderInterface( s_render_interface );
+	Rocket::Core::SetSystemInterface( s_system_interface );
+
+	return Rocket::Core::Initialise();
+}
+
+void nv::rocket_shutdown()
+{
+	Rocket::Core::Shutdown();
+	delete s_render_interface;
+	delete s_system_interface;
+	delete s_file_interface;
+}
Index: trunk/src/wx/wx_canvas.cc
===================================================================
--- trunk/src/wx/wx_canvas.cc	(revision 352)
+++ trunk/src/wx/wx_canvas.cc	(revision 352)
@@ -0,0 +1,90 @@
+// Copyright (C) 2014 ChaosForge Ltd 
+// http://chaosforge.org/
+//
+// This file is part of NV Libraries.
+// For conditions of distribution and use, see copyright notice in nv.hh
+
+#include <nv/wx/wx_canvas.hh>
+
+wxBEGIN_EVENT_TABLE( nv::wx_gl_canvas, wxWindow )
+EVT_PAINT( nv::wx_gl_canvas::on_paint )
+wxEND_EVENT_TABLE()
+
+nv::wx_gl_canvas::wx_gl_canvas( wxWindow *parent )
+	: wxWindow( parent, wxID_ANY, wxDefaultPosition, wxDefaultSize,
+	wxFULL_REPAINT_ON_RESIZE )
+{
+	nv::load_gl_no_context();
+	wxClientDC dc( this );
+	m_wm = new nv::sdl::window_manager;
+	m_device = new nv::gl_device();
+	m_window = new nv::gl_window( m_device, m_wm, new nv::sdl::input(), GetHWND(), dc.GetHDC() );
+	m_context = m_window->get_context();
+}
+
+nv::wx_gl_canvas::~wx_gl_canvas()
+{
+	delete m_window;
+	delete m_device;
+}
+
+void nv::wx_gl_canvas::on_paint( wxPaintEvent& )
+{
+	const wxSize client_size = GetClientSize();
+	m_context->set_viewport( nv::ivec4( nv::ivec2(), client_size.x, client_size.y ) );
+	on_update();
+}
+
+void nv::wx_gl_canvas::stop()
+{
+	Disconnect( wxEVT_IDLE, wxIdleEventHandler( wx_gl_canvas::on_idle ) );
+	//m_update_timer->Stop();
+}
+
+void nv::wx_gl_canvas::start()
+{
+	Connect( wxID_ANY, wxEVT_IDLE, wxIdleEventHandler( wx_gl_canvas::on_idle ) );
+	//m_update_timer->Start(20);
+}
+
+void nv::wx_gl_canvas::on_idle( wxIdleEvent& evt )
+{
+	if ( m_render )
+	{
+		on_update();
+		evt.RequestMore(); // render continuously, not only once on idle
+	}
+}
+
+nv::wx_log_text_ctrl_sink::wx_log_text_ctrl_sink( wxTextCtrl* a_text_ctrl ) : m_text_ctrl( a_text_ctrl )
+{
+
+}
+
+void nv::wx_log_text_ctrl_sink::log( nv::log_level level, const std::string& message )
+{
+	wxString str;
+	str << timestamp() << " [" << padded_level_name( level ) << "] " << message << "\n";
+	m_text_ctrl->AppendText( str );
+}
+
+nv::wx_app_base::wx_app_base()
+{
+	static nv::logger log( nv::LOG_TRACE );
+	log.add_sink( new nv::log_file_sink( "log.txt" ), nv::LOG_TRACE );
+	NV_LOG( nv::LOG_NOTICE, "Logging started" );
+}
+
+bool nv::wx_app_base::OnInit()
+{
+	if ( !wxApp::OnInit() )
+		return false;
+
+	return initialize();
+}
+
+int nv::wx_app_base::OnExit()
+{
+	NV_LOG( nv::LOG_NOTICE, "Logging stopped" );
+	return wxApp::OnExit();
+}
Index: trunk/tests/handle_test/handle_test.cc
===================================================================
--- trunk/tests/handle_test/handle_test.cc	(revision 352)
+++ trunk/tests/handle_test/handle_test.cc	(revision 352)
@@ -0,0 +1,113 @@
+#include <nv/core/logger.hh>
+#include <nv/core/handle.hh>
+#include <nv/core/math.hh>
+#include <nv/core/string.hh>
+#include <nv/core/random.hh>
+
+int main(int, char* [])
+{
+	nv::random& r = nv::random::get();
+	r.randomize();
+
+	nv::logger log(nv::LOG_TRACE);
+	log.add_sink( new nv::log_file_sink("log.txt"), nv::LOG_TRACE );
+	log.add_sink( new nv::log_console_sink(), nv::LOG_TRACE );
+	
+	NV_LOG( nv::LOG_NOTICE, "Logging started" );
+
+	{
+		nv::handle_manager< nv::handle<> > manager;
+		std::vector< nv::handle<> > created;
+
+		for ( int i = 0; i < 32000; ++i )
+		{
+			if ( created.size() > 0 && r.urange( 0, 2 ) == 2 )
+			{
+				nv::uint32 roll = r.urange( 0, created.size() - 1 );
+				nv::handle<> h = created[roll];
+				NV_ASSERT( manager.is_valid( h ), "handle not valid!" );
+				created.erase( created.begin() + roll );
+				manager.free_handle( h );
+			}
+			else
+			{
+				created.push_back( manager.create_handle() );
+			}
+		}
+
+		for ( const nv::handle<>& h : created )
+		{
+			NV_ASSERT( manager.is_valid( h ), "handle not valid!" );
+		}
+
+
+		for ( int i = 0; i < 32000; ++i )
+		{
+			if ( created.size() > 0 && r.urange( 0, 1 ) == 1 )
+			{
+				nv::uint32 roll = r.urange( 0, created.size() - 1 );
+				nv::handle<> h = created[roll];
+				created.erase( created.begin() + roll );
+				manager.free_handle( h );
+			}
+			else
+			{
+				created.push_back( manager.create_handle() );
+			}
+		}
+
+		for ( const nv::handle<>& h : created )
+		{
+			NV_ASSERT( manager.is_valid( h ), "handle not valid!" );
+		}
+
+		while ( created.size() > 0 )
+		{
+			nv::uint32 roll = r.urange( 0, created.size() - 1 );
+			nv::handle<> h = created[roll];
+			created.erase( created.begin() + roll );
+			manager.free_handle( h );
+		}
+
+		for ( const nv::handle<>& h : created )
+		{
+			NV_ASSERT( manager.is_valid( h ), "handle not valid!" );
+		}
+
+		for ( int i = 0; i < 32000; ++i )
+		{
+			if ( created.size() > 0 && r.urange( 0, 1 ) == 1 )
+			{
+				nv::uint32 roll = r.urange( 0, created.size() - 1 );
+				nv::handle<> h = created[roll];
+				created.erase( created.begin() + roll );
+				manager.free_handle( h );
+			}
+			else
+			{
+				created.push_back( manager.create_handle() );
+			}
+		}
+
+		for ( const nv::handle<>& h : created )
+		{
+			NV_ASSERT( manager.is_valid( h ), "handle not valid!" );
+		}
+
+		while ( created.size() > 0 )
+		{
+			nv::uint32 roll = r.urange( 0, created.size() - 1 );
+			nv::handle<> h = created[roll];
+			created.erase( created.begin() + roll );
+			manager.free_handle( h );
+		}
+
+		NV_LOG( nv::LOG_NOTICE, "Done" );
+
+	}
+	
+	NV_LOG( nv::LOG_NOTICE, "Logging stopped" );
+
+	return 0;
+}
+
Index: trunk/tests/handle_test/handle_test.lua
===================================================================
--- trunk/tests/handle_test/handle_test.lua	(revision 352)
+++ trunk/tests/handle_test/handle_test.lua	(revision 352)
@@ -0,0 +1,8 @@
+project "nv_handle_test"
+	kind "ConsoleApp"
+	files { "handle_test.cc" }
+	includedirs { "../../" }
+	targetname "handle_test"
+	links { "nv-core" }
+	targetdir "../../bin"	
+ 
Index: trunk/tests/handle_test/premake4.lua
===================================================================
--- trunk/tests/handle_test/premake4.lua	(revision 352)
+++ trunk/tests/handle_test/premake4.lua	(revision 352)
@@ -0,0 +1,18 @@
+solution "nv_handle_test"
+	configurations { "debug", "release" }
+
+  	language "C++"
+	flags { "ExtraWarnings", "NoPCH" }
+
+	configuration "debug"
+		defines { "DEBUG" }
+		flags { "Symbols", "StaticRuntime" }
+		objdir ("../../".._ACTION.."/debug")
+
+	configuration "release"
+		defines { "NDEBUG" }
+		flags { "Optimize", "StaticRuntime" }
+		objdir ("../../".._ACTION.."/release")
+
+	dofile("handle_test.lua")
+	dofile("../../nv.lua")
