Index: /trunk/nv/bullet/bullet_world.hh
===================================================================
--- /trunk/nv/bullet/bullet_world.hh	(revision 520)
+++ /trunk/nv/bullet/bullet_world.hh	(revision 520)
@@ -0,0 +1,60 @@
+// Copyright (C) 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 bullet_world.hh
+ * @author Kornel Kisielewicz
+ * @brief bullet_world
+ */
+
+#ifndef NV_BULLET_WORLD_HH
+#define NV_BULLET_WORLD_HH
+
+#include <nv/common.hh>
+#include <nv/interface/physics_world.hh>
+
+namespace nv
+{
+	class bullet_world : public physics_world
+	{
+	public:
+		bullet_world();
+		virtual int step_simulation( float dtime, int max_sub_steps = 1, float fixed_time_step = 1.0f / 60.0f );
+		virtual void set_gravity( const vec3& gravity );
+		virtual collision_shape create_sphere( float radius );
+		virtual collision_shape create_capsule( float radius, float height );
+		virtual collision_shape create_box( const vec3& half_extens );
+		virtual collision_shape create_static_plane( const vec3& norm, float cst );
+		virtual collision_shape create_mesh( array_view< vec3 > vtx, array_view< int > idx );
+		virtual rigid_body create_rigid_body( float mass, const transform& tr, collision_shape shape, const vec3& com_offset = vec3() );
+		virtual constraint create_hinge_constraint( rigid_body a, const transform& ta, rigid_body b, const transform& tb, const vec2& low_high, const vec3& params = vec3( 0.9f, 0.3f, 1.0f ) );
+		virtual constraint create_cone_twist_constraint( rigid_body a, const transform& ta, rigid_body b, const transform& tb, const vec3& sst, const vec3& params = vec3( 1.0f, 0.3f, 1.0f ) );
+		virtual void add_rigid_body( rigid_body body );
+		virtual void remove_rigid_body( rigid_body body );
+		virtual void add_constraint( constraint cons );
+		virtual void set_rigid_body_damping( rigid_body body, float linear, float angular );
+		virtual void set_rigid_body_deactivation_time( rigid_body body, float time );
+		virtual void set_rigid_body_activation_state( rigid_body body, activation_state state );
+		virtual void set_rigid_body_sleeping_thresholds( rigid_body body, float linear, float angular );
+		virtual void set_rigid_body_linear_velocity( rigid_body body, const vec3& velocity );
+		virtual void set_rigid_body_ccd( rigid_body body, float radius, float threshold );
+
+		virtual nv::transform get_world_transform( rigid_body body );
+		virtual void set_world_transform( rigid_body body, const nv::transform& tr );
+		virtual int get_collision_flags( rigid_body body );
+		virtual void set_collision_flags( rigid_body body, int flags );
+
+		virtual bool ray_test( const vec3& from, const vec3& to, vec3& hpos, vec3& hnorm, bool static_only = false );
+		virtual void release( collision_shape );
+		virtual void release( rigid_body );
+		virtual void release( constraint );
+	protected:
+		void* world;
+	};
+
+}
+
+#endif // NV_BULLET_WORLD_HH
Index: /trunk/nv/core/aabb.hh
===================================================================
--- /trunk/nv/core/aabb.hh	(revision 520)
+++ /trunk/nv/core/aabb.hh	(revision 520)
@@ -0,0 +1,41 @@
+// 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 aabb.hh
+ * @author Kornel Kisielewicz
+ * @brief axis aligned bounding box utility class
+ */
+
+#ifndef NV_CORE_AABB_HH
+#define NV_CORE_AABB_HH
+
+#include <nv/common.hh>
+#include <nv/stl/math.hh>
+#include <nv/core/transform.hh>
+#include <nv/stl/utility/common.hh>
+
+namespace nv
+{
+
+	class aabb
+	{
+	public:
+		aabb() = default;
+		aabb( const transform& root, const vec3& hextents )
+			: m_root( root ), m_hextents( hextents ) {}
+		aabb( const aabb& ) = default;
+		aabb( aabb&& ) = default;
+		const vec3& get_hextents() { return m_hextents; }
+		const transform& get_root() { return m_root; }
+	protected:
+		transform m_root;
+		vec3      m_hextents;
+	};
+
+}
+
+#endif // NV_CORE_AABB_HH
Index: /trunk/nv/core/bullet_world.hh
===================================================================
--- /trunk/nv/core/bullet_world.hh	(revision 520)
+++ /trunk/nv/core/bullet_world.hh	(revision 520)
@@ -0,0 +1,24 @@
+// Copyright (C) 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 bullet_device.hh
+ * @author Kornel Kisielewicz
+ * @brief bullet_device
+ */
+
+#ifndef NV_BULLET_DEVICE_HH
+#define NV_BULLET_DEVICE_HH
+
+#include <nv/common.hh>
+
+namespace nv
+{
+
+
+}
+
+#endif // NV_BULLET_DEVICE_HH
Index: /trunk/nv/ecs/ecs.hh
===================================================================
--- /trunk/nv/ecs/ecs.hh	(revision 519)
+++ /trunk/nv/ecs/ecs.hh	(revision 520)
@@ -17,4 +17,5 @@
 #include <nv/stl/string.hh>
 #include <nv/stl/handle.hh>
+#include <nv/core/types.hh>
 
 namespace nv
@@ -122,9 +123,10 @@
 
 
-		template < typename HANDLE = handle<> >
+		template < typename Handle = handle<>, typename Time = f32 >
 		class ecs
 		{
 		public:
-			typedef HANDLE handle_type;
+			typedef Time   time_type;
+			typedef Handle handle_type;
 
 			typedef uint32 message_type;
@@ -134,13 +136,30 @@
 				message_type type;
 				handle_type  entity;
-				uint8        payload[128 - sizeof( uint32 ) - sizeof( handle_type )];
+				time_type    time;
+				uint8        payload[128 - sizeof( uint32 ) - sizeof( handle_type ) - sizeof( time_type )];
 			};
 
-			class component_interface
+			struct message_compare_type
+			{
+				bool operator()( const message& l, const message& r )
+				{
+					return l.time > r.time;
+				}
+			};
+
+			class receiver_interface
 			{
 			public:
+				virtual void update( time_type dtime ) = 0;
 				virtual bool handle_message( const message& ) = 0;
 				virtual void clear() = 0;
+			};
+
+			class component_interface : public receiver_interface
+			{
+			public:
 				virtual void remove( handle_type h ) = 0;
+				virtual void* get_raw( handle_type h ) = 0;
+				virtual const void* get_raw( handle_type h ) const = 0;
 			};
 
@@ -153,13 +172,20 @@
 			}
 
-			template < typename COMPONENT >
-			void register_component( string_view /*name*/, component_interface* c )
+			void register_receiver( string_view /*name*/, receiver_interface* c )
+			{
+				m_receivers.push_back( c );
+			}
+
+			template < typename Component >
+			void register_component( string_view name, component_interface* c )
 			{
 				m_components.push_back( c );
+				register_receiver( name, c );
+				m_component_map[thash64::create<Component>()] = c;
 			}
 
 			bool dispatch( const message& m )
 			{
-				for ( auto c : m_components )
+				for ( auto c : m_receivers )
 					c->handle_message( m );
 				return true;
@@ -169,9 +195,24 @@
 			bool dispatch( handle_type h, Args&&... args )
 			{
-				message m{ Payload::message_id, h };
+				message m{ Payload::message_id, h, time_type(0) };
 				new( &m.payload ) Payload{ nv::forward<Args>( args )... };
 				return dispatch( m );
 			}
 
+			bool queue( const message& m )
+			{
+				push_event( m );
+				return true;
+			}
+
+			template < typename Payload, typename ...Args >
+			bool queue( handle_type h, time_type delay, Args&&... args )
+			{
+				message m{ Payload::message_id, h, m_time + delay };
+				new( &m.payload ) Payload{ nv::forward<Args>( args )... };
+				return queue( m );
+			}
+
+
 			handle_type create()
 			{
@@ -179,7 +220,57 @@
 			}
 
+			void update( time_type dtime )
+			{
+				if ( dtime == time_type(0) ) return;
+				m_time += dtime;
+				while ( !m_queue.empty() && m_queue.front().time <= m_time )
+				{
+					message msg = m_queue.front();
+					pop_event();
+					dispatch( msg );
+				}
+
+				for ( auto c : m_receivers )
+					c->update( dtime );
+			}
+
+			time_type update_step()
+			{
+				time_type before = m_time;
+				if ( !m_queue.empty() )
+				{
+					message msg = m_queue.front();
+					m_time = msg.time;
+					pop_event();
+					dispatch( msg );
+					if ( before != m_time )
+						for ( auto c : m_receivers )
+							c->update( m_time - before );
+				}
+				return m_time;
+			}
+
+			time_type get_time() const { return m_time; }
+
+			bool events_pending() const
+			{
+				return !m_queue.empty();
+			}
+
+			const message& top_event() const
+			{
+				return m_queue.front();
+			}
+
+			void reset_events()
+			{
+				m_queue.clear();
+				m_time = time_type(0);
+			}
+
 			void clear()
 			{
-				for ( auto c : m_components )
+				reset_events();
+				for ( auto c : m_receivers )
 					c->clear();
 				m_handles.clear();
@@ -198,7 +289,36 @@
 			}
 
+			template < typename Component >
+			Component* get( handle_type h )
+			{
+				return static_cast< Component* >( m_component_map[thash64::create< Component >()]->get_raw( h ) );
+			}
+
+			template < typename Component >
+			const Component* get( handle_type h ) const
+			{
+				return static_cast<const Component*>( m_component_map[thash64::create< Component >()]->get_raw( h ) );
+			}
+
 		protected:
-			handle_manager< handle_type > m_handles;
-			vector< component_interface* > m_components;
+			void push_event( const message& msg )
+			{
+				m_queue.push_back( msg );
+				push_heap( m_queue.begin(), m_queue.end(), m_compare );
+			}
+
+			void pop_event()
+			{
+				pop_heap( m_queue.begin(), m_queue.end(), m_compare );
+				m_queue.pop_back();
+			}
+
+			handle_manager< handle_type >               m_handles;
+			vector< component_interface* >              m_components;
+			vector< receiver_interface* >               m_receivers;
+			hash_store< thash64, component_interface* > m_component_map;
+			time_type                                   m_time = time_type(0);
+			vector< message >                           m_queue;
+			message_compare_type                        m_compare;
 		};
 
@@ -210,4 +330,5 @@
 			typedef typename ecs_type::message             message_type;
 			typedef typename ecs_type::handle_type         handle_type;
+			typedef typename ecs_type::time_type           time_type;
 			typedef index_table< handle_type >             index_table_type;
 			typedef COMPONENT                              value_type;
@@ -252,6 +373,13 @@
 			}
 
+			virtual void update( time_type /*dtime*/ )
+			{
+				// no-op
+			}
+
 			virtual void clear()
 			{
+				for ( uint32 i = 0; i < m_index.size(); ++i )
+					destroy( &m_data[i] );
 				m_index.clear();
 				m_data.clear();
@@ -270,6 +398,22 @@
 			}
 
+			void* get_raw( handle_type h )
+			{
+				index_type i = m_index.get( h );
+				return i >= 0 ? &( m_data[unsigned( i )] ) : nullptr;
+			}
+
+			const void* get_raw( handle_type h ) const
+			{
+				index_type i = m_index.get( h );
+				return i >= 0 ? &( m_data[unsigned( i )] ) : nullptr;
+			}
+
+
 			virtual void remove( handle_type h )
 			{
+				value_type* v = get( h );
+				if ( v == nullptr ) return;
+				destroy( v );
 				index_type dead_eindex = m_index.remove_swap( h );
 				if ( dead_eindex == -1 ) return;
@@ -281,13 +425,7 @@
 			}
 
-			virtual void remove( index_type i )
-			{
-				index_type dead_eindex = m_index.remove_swap( i );
-				if ( dead_eindex == -1 ) return;
-				if ( dead_eindex != static_cast<index_type>( m_data.size() - 1 ) )
-				{
-					m_data[unsigned( dead_eindex )] = move( m_data.back() );
-				}
-				m_data.pop_back();
+			virtual void destroy( value_type* )
+			{
+				// cleanup
 			}
 
@@ -295,4 +433,9 @@
 			{
 				return true;
+			}
+
+			~component()
+			{
+				clear();
 			}
 
@@ -318,10 +461,33 @@
 		};
 
-		template < typename COMPONENTS >
-		class system
+		template < typename ECS >
+		class receiver : public ECS::receiver_interface
 		{
-
+		public:
+			typedef ECS                                    ecs_type;
+			typedef typename ecs_type::message             message_type;
+			typedef typename ecs_type::handle_type         handle_type;
+			typedef typename ecs_type::time_type           time_type;
+
+			receiver( ecs_type& a_ecs, string_view a_name ) : m_ecs( a_ecs )
+			{
+				m_ecs.register_receiver( a_name, this );
+			}
+			virtual void update( time_type /*dtime*/ )
+			{
+				// no-op
+			}
+
+			virtual void clear()
+			{
+				// no-op
+			}
+			virtual bool handle_message( const message_type& )
+			{
+				return false;
+			}
+		protected:
+			ecs_type&        m_ecs;
 		};
-
 	}
 
Index: /trunk/nv/engine/animation.hh
===================================================================
--- /trunk/nv/engine/animation.hh	(revision 519)
+++ /trunk/nv/engine/animation.hh	(revision 520)
@@ -109,8 +109,11 @@
 			return m_current_state;
 		}
-		void add_transition( const animator_transition_data& data, uint32 source )
-		{
+		float add_transition( const animator_transition_data& data, uint32 source )
+		{
+			float duration = 0.0f;
 			if ( m_transitions.size() > 0 )
 			{
+				for ( const auto& t : m_transitions )
+					duration += t.duration - t.time;
 				auto& last = m_transitions.back();
 				if ( last.target == source && data.target == last.source )
@@ -118,8 +121,10 @@
 					nv::swap( last.target, last.source );
 					last.time = last.duration - last.time;
-					return;
+					/* this duration might be inaccurate? */
+					return duration;
 				}
 			}
 			m_transitions.push_back( animator_transition_instance( data, source ) );
+			return duration + m_transitions.back().duration;
 		}
 		void reset()
@@ -198,6 +203,7 @@
 		}
 		
-		void queue_event( const animator_data* data, shash64 name )
-		{
+		float queue_event( const animator_data* data, shash64 name )
+		{
+			float duration = 0.0f;
 			for ( uint32 i = 0; i < m_layers.size(); ++i )
 				// what about activating nonactive layers?
@@ -209,10 +215,12 @@
 					if ( it != state.transitions.end() )
 					{
-						m_layers[i].add_transition( it->second, uint32( last_state ) );
+						float d = m_layers[i].add_transition( it->second, uint32( last_state ) );
+						duration = nv::max( d, duration );
 					}
 				}
-		}
-
-		void update( const animator_data* data, float dtime )
+			return duration;
+		}
+
+		void update( const animator_data* data, float dtime, bool do_delocalize = true )
 		{
 			for ( uint32 i = 0; i < m_layers.size(); ++i )
@@ -235,4 +243,9 @@
 				}
 
+			if ( do_delocalize )
+				m_transforms.delocalize( data->poses->get_tree() );
+		}
+		void delocalize( const animator_data* data )
+		{
 			m_transforms.delocalize( data->poses->get_tree() );
 		}
@@ -275,5 +288,16 @@
 		}
 
-	protected:
+		const skeleton_transforms& stored()
+		{
+			return m_stored;
+		}
+
+		void store()
+		{
+			m_stored.assign( m_transforms );
+		}
+
+
+//	protected:
 
 		void animate_layer( const animator_layer_data& layer, const animator_transition_instance& transition, pose_data_set* pose_data, float t )
@@ -349,8 +373,8 @@
 		}
 
+
 		vector< animator_layer_instance > m_layers;
 		skeleton_transforms               m_transforms;
-
-
+		skeleton_transforms               m_stored;
 	};
 
Index: /trunk/nv/engine/default_resource_manager.hh
===================================================================
--- /trunk/nv/engine/default_resource_manager.hh	(revision 519)
+++ /trunk/nv/engine/default_resource_manager.hh	(revision 520)
@@ -26,4 +26,5 @@
 #include <nv/engine/model_manager.hh>
 #include <nv/engine/particle_manager.hh>
+#include <nv/engine/ragdoll_manager.hh>
 
 namespace nv
@@ -58,5 +59,5 @@
 		const type_database* get_type_db() { return m_lua->get_type_data()->get_type_database(); }
 
-		virtual void initialize( lua::state* lua );
+		virtual void initialize( lua::state* lua, physics_world* world );
 		virtual void reload_data();
 	protected:
@@ -73,4 +74,5 @@
 		model_manager*         m_models;
 		particle_manager*      m_particles;
+		ragdoll_manager*       m_ragdolls;
 	};
 
Index: /trunk/nv/engine/light.hh
===================================================================
--- /trunk/nv/engine/light.hh	(revision 519)
+++ /trunk/nv/engine/light.hh	(revision 520)
@@ -33,5 +33,5 @@
 	{
 		vec3 position;
-		vec3 color;
+		vec4 color;
 		vec4 direction;
 		float angle;
@@ -43,8 +43,10 @@
 		light_data() : color( 1.0 ), direction( 0.0f, -1.0f, 0.0f, 1.0f ), angle( 0.6f ), umbra_angle( 0.2f ), range( 4.0 ), smooth( 0 ), shadow( shadow_type::ACTIVE ) {}
 		light_data( const vec3& a_position, const vec3& a_color, shadow_type type )
-			: position( a_position ), color( a_color )
+			: position( a_position ), color( a_color, 1.0f )
 			, direction( 0.0f, -1.0f, 0.0f, 1.0f ), angle( 0.6f ), umbra_angle( 0.2f ), range( 4.0 ), smooth( 0 ), shadow( type ) {}
 		light_data( const light_data& ) = default;
 	};
+	
+	NV_RTTI_DECLARE( nv::light_data )
 
 	struct light_data_distance_compare
@@ -89,5 +91,5 @@
 
 				m_blocks[i].position  = vec4( lights[i]->position, 1.0f );
-				m_blocks[i].color     = vec4( lights[i]->color, 1.0f );
+				m_blocks[i].color     = vec4( vec3( lights[i]->color ) * lights[i]->color.w, 1.0f );
 				m_blocks[i].direction = lights[i]->direction;
 				m_blocks[i].params    = vec4( lights[i]->angle, lights[i]->umbra_angle, lights[i]->range, 1.0f );
Index: /trunk/nv/engine/particle_engine.hh
===================================================================
--- /trunk/nv/engine/particle_engine.hh	(revision 519)
+++ /trunk/nv/engine/particle_engine.hh	(revision 520)
@@ -1,3 +1,3 @@
-// Copyright (C) 2014-2015 ChaosForge Ltd
+// Copyright (C) 2014-2016 ChaosForge Ltd
 // http://chaosforge.org/
 //
@@ -16,6 +16,8 @@
 #include <nv/gfx/texture_atlas.hh>
 #include <nv/core/resource.hh>
+#include <nv/core/random.hh>
 #include <nv/interface/scene_node.hh>
 #include <nv/interface/context.hh>
+#include <nv/engine/particle_group.hh>
 
 namespace nv
@@ -27,7 +29,6 @@
 	struct particle_emitter_data;
 	struct particle_affector_data;
-	struct particle;
 
-	typedef void( *particle_emitter_func )( const particle_emitter_data*, particle*, uint32 count );
+	typedef void( *particle_emitter_func )( random_base* r, const particle_emitter_data*, particle*, uint32 count );
 	typedef void( *particle_affector_func )( const particle_affector_data*, particle*, float factor, uint32 count );
 	typedef bool( *particle_affector_init_func )( lua::table_guard* table, particle_affector_data* data );
@@ -37,50 +38,4 @@
 		particle_affector_func      process;
 		particle_affector_init_func init;
-	};
-
-	enum class particle_orientation
-	{
-		POINT,
-		ORIENTED,
-		ORIENTED_COMMON,
-		PERPENDICULAR,
-		PERPENDICULAR_COMMON,
-	};
-	enum class particle_origin
-	{
-		CENTER,
-		TOP_LEFT,
-		TOP_CENTER,
-		TOP_RIGHT,
-		CENTER_LEFT,
-		CENTER_RIGHT,
-		BOTTOM_LEFT,
-		BOTTOM_CENTER,
-		BOTTOM_RIGHT,
-	};
-
-	struct particle_vtx
-	{
-		vec3 position;
-		vec2 texcoord;
-		vec4 color;
-	};
-
-	struct particle_quad
-	{
-		particle_vtx data[6];
-	};
-
-	struct particle
-	{
-		vec3   position;
-		vec4   color;
-		vec3   velocity;
-		vec2   size;
-		vec2   tcoord_a;
-		vec2   tcoord_b;
-		float  rotation;
-		float  lifetime;
-		float  death;
 	};
 
@@ -127,12 +82,7 @@
 	};
 
-	struct particle_system_data
+	struct particle_system_data : particle_group_settings
 	{
 		uint32 quota;
-		vec3   common_up;
-		vec3   common_dir;
-		bool   accurate_facing;
-		particle_orientation   orientation;
-		particle_origin        origin;
 		uint32                 emitter_count;
 		particle_emitter_data  emitters[MAX_PARTICLE_EMITTERS];
@@ -141,6 +91,4 @@
 	};
 
-	struct particle_system_group_tag {};
-	typedef handle< uint32, 16, 16, particle_system_group_tag > particle_system_group;
 	struct particle_system_tag {};
 	typedef handle< uint32, 16, 16, particle_system_tag > particle_system;
@@ -151,26 +99,9 @@
 		particle_emitter_info emitters[MAX_PARTICLE_EMITTERS];
 
-		uint32                count;
-		vec2                  texcoords[2];
-		particle_system_group group;
+		uint32         count;
+		vec2           texcoords[2];
+		particle_group group;
 
 		const particle_system_data*    data;
-	};
-
-	struct particle_system_group_info
-	{
-		uint32                count;
-		uint32                quota;
-		vertex_array          vtx_array;
-		buffer                vtx_buffer;
-		bool                  local;
-		particle_quad*        quads;
-		uint32                ref_counter;
-	};
-
-	struct particle_render_data
-	{
-		uint32                count;
-		vertex_array          vtx_array;
 	};
 
@@ -178,19 +109,18 @@
 
 NV_RTTI_DECLARE_NAME( nv::particle_system_data, "particle_system_data" )
-NV_RTTI_DECLARE_NAME( nv::particle_orientation, "particle_orientation" )
-NV_RTTI_DECLARE_NAME( nv::particle_origin,      "particle_origin" )
 
-namespace nv {
+namespace nv
+{
 
 	class particle_engine
 	{
 	public:
-		particle_engine( context* a_context, bool debug_data = false );
+		particle_engine( context* a_context, random_base* rng, particle_group_manager* groups, bool debug_data = false );
 		void reset();
-		void prepare( particle_system_group group );
-		particle_system_group create_group( uint32 max_particles );
-		particle_system create_system( resource< particle_system_data > rdata, particle_system_group group );
-		particle_system create_system( const particle_system_data* data, particle_system_group group );
-		void release( particle_system_group group );
+		void prepare( particle_group group );
+		particle_group create_group( uint32 max_particles );
+		particle_system create_system( resource< particle_system_data > rdata, particle_group group );
+		particle_system create_system( const particle_system_data* data, particle_group group );
+		void release( particle_group group );
 		void release( particle_system system );
 		void update( particle_system system, transform model, float dtime );
@@ -205,7 +135,8 @@
 		static const vector< particle_emitter_func >* get_debug_emitter_list() { return m_debug_emitter_list; }
 		static const vector< particle_affector_func >* get_debug_affector_list() { return m_debug_affector_list; }
-		static const type_entry* get_debug_type( particle_affector_init_func f );
-		static void register_types( type_database* db );
-		particle_render_data get_render_data( particle_system_group group );
+		static const type_entry* get_debug_type( particle_affector_func f );
+		static void register_types( type_database* db, bool debug_data = false );
+		particle_render_data get_render_data( particle_group group );
+		particle_group_manager* get_particle_manager() { return m_pgm; }
 		~particle_engine();
 	private:
@@ -213,5 +144,4 @@
 		void register_standard_emitters();
 		void register_standard_affectors();
-		void generate_data( particle_system_info* info, const scene_state& s );
 		void destroy_particles( particle_system_info* info, float dtime );
 		void create_particles( particle_system_info* info, transform tr, float dtime );
@@ -219,8 +149,9 @@
 		void update_emitters( particle_system_info* info, float dtime );
 
+		particle_group_manager* m_pgm;
 		context* m_context;
+		random_base* m_rng;
 
 		handle_store< particle_system_info, particle_system >             m_systems;
-		handle_store< particle_system_group_info, particle_system_group > m_groups;
 
 		static hash_store< shash64, particle_emitter_func >          m_emitters;
Index: /trunk/nv/engine/particle_group.hh
===================================================================
--- /trunk/nv/engine/particle_group.hh	(revision 520)
+++ /trunk/nv/engine/particle_group.hh	(revision 520)
@@ -0,0 +1,125 @@
+// 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.
+
+#ifndef NV_ENGINE_PARTICLE_GROUP
+#define NV_ENGINE_PARTICLE_GROUP
+
+#include <nv/common.hh>
+#include <nv/stl/math.hh>
+#include <nv/stl/vector.hh>
+#include <nv/stl/handle.hh>
+#include <nv/interface/context.hh>
+
+namespace nv
+{
+
+	enum class particle_orientation
+	{
+		POINT,
+		ORIENTED,
+		ORIENTED_COMMON,
+		PERPENDICULAR,
+		PERPENDICULAR_COMMON,
+	};
+	enum class particle_origin
+	{
+		CENTER,
+		TOP_LEFT,
+		TOP_CENTER,
+		TOP_RIGHT,
+		CENTER_LEFT,
+		CENTER_RIGHT,
+		BOTTOM_LEFT,
+		BOTTOM_CENTER,
+		BOTTOM_RIGHT,
+	};
+
+	struct particle_vtx
+	{
+		vec3 position;
+		vec2 texcoord;
+		vec4 color;
+	};
+
+	struct particle_quad
+	{
+		particle_vtx data[6];
+	};
+
+	struct particle
+	{
+		vec3   position;
+		vec4   color;
+		vec3   velocity;
+		vec2   size;
+		vec2   tcoord_a;
+		vec2   tcoord_b;
+		float  rotation;
+		float  lifetime;
+		float  death;
+	};
+
+	struct particle_group_settings
+	{
+		vec3   common_up;
+		vec3   common_dir;
+		bool   accurate_facing;
+		particle_orientation   orientation;
+		particle_origin        origin;
+	};
+
+	struct particle_group_tag {};
+	typedef handle< uint32, 16, 16, particle_group_tag > particle_group;
+
+	struct particle_group_info
+	{
+		uint32                count;
+		uint32                quota;
+		vertex_array          vtx_array;
+		buffer                vtx_buffer;
+		bool                  local;
+		particle_quad*        quads;
+		uint32                ref_counter;
+	};
+
+	struct particle_render_data
+	{
+		uint32                count;
+		vertex_array          vtx_array;
+	};
+
+	class particle_group_manager
+	{
+	public:
+		particle_group_manager( context* a_context );
+		particle_group create_group( uint32 max_particles );
+		void release( particle_group group );
+		void prepare( particle_group group );
+		void reset();
+		void generate_data(
+			array_view< particle > particles,
+			const particle_group_settings* data,
+			particle_group group,
+			const scene_state& s
+		);
+		particle_render_data get_render_data( particle_group group );
+		~particle_group_manager();
+
+		bool ref( particle_group group );
+		bool unref( particle_group group );
+	protected:
+		void clear();
+
+		context* m_context;
+		handle_store< particle_group_info, particle_group > m_groups;
+	};
+
+}
+
+NV_RTTI_DECLARE_NAME( nv::particle_orientation, "particle_orientation" )
+NV_RTTI_DECLARE_NAME( nv::particle_origin, "particle_origin" )
+
+#endif // NV_ENGINE_PARTICLE_GROUP
Index: /trunk/nv/engine/ragdoll_manager.hh
===================================================================
--- /trunk/nv/engine/ragdoll_manager.hh	(revision 520)
+++ /trunk/nv/engine/ragdoll_manager.hh	(revision 520)
@@ -0,0 +1,63 @@
+// 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 ragdoll_manager.hh
+ * @author Kornel Kisielewicz
+ * @brief ragdoll manager
+ */
+
+#ifndef NV_ENGINE_RAGDOLL_MANAGER_HH
+#define NV_ENGINE_RAGDOLL_MANAGER_HH
+
+#include <nv/common.hh>
+#include <nv/interface/physics_world.hh>
+#include <nv/engine/resource_system.hh>
+#include <nv/engine/model_manager.hh>
+
+namespace nv
+{
+	struct ragdoll_part_data
+	{
+		shash64         name;
+		collision_shape shape;
+		float           radius;
+		float           length;
+		uint32          bone_id;
+		sint32          parent_idx;
+		f32             mass;
+		bool            cone_twist;
+		vec3            limits;
+	};
+
+	struct ragdoll_data
+	{
+		vector< ragdoll_part_data > parts;
+		vector< bool >              bone_mask;
+	};
+
+	NV_RTTI_DECLARE_NAME( nv::ragdoll_data, "ragdoll_data" )
+
+	class ragdoll_manager : public lua_resource_manager< ragdoll_data >
+	{
+	public:
+		ragdoll_manager( model_manager* model );
+		virtual void initialize( lua::state* state );
+		virtual void initialize( physics_world* world ) { m_world = world; }
+		virtual string_view get_storage_name() const { return "ragdolls"; }
+		virtual string_view get_resource_name() const { return "ragdoll"; }
+	protected:
+		virtual bool load_resource( lua::table_guard& table, shash64 id );
+		virtual void release( ragdoll_data* p );
+		bool load_ragdoll( lua::table_guard& table, resource< animator_bind_data > rbind, ragdoll_data* data, int pindex );
+	private:
+		physics_world* m_world;
+		model_manager* m_model;
+	};
+
+}
+
+#endif // NV_ENGINE_RAGDOLL_MANAGER_HH
Index: /trunk/nv/gfx/debug_draw.hh
===================================================================
--- /trunk/nv/gfx/debug_draw.hh	(revision 519)
+++ /trunk/nv/gfx/debug_draw.hh	(revision 520)
@@ -37,4 +37,5 @@
 		void push_aabox( const vec3& a, const vec3& b, const vec3& color );
 		void push_gizmo( const nv::transform& tr, float length );
+		void push_wire_capsule( const transform& tr, float radius, float height );
 		~debug_data();
 	private:
Index: /trunk/nv/gfx/mesh_creator.hh
===================================================================
--- /trunk/nv/gfx/mesh_creator.hh	(revision 519)
+++ /trunk/nv/gfx/mesh_creator.hh	(revision 520)
@@ -10,4 +10,5 @@
 #include <nv/common.hh>
 #include <nv/stl/math.hh>
+#include <nv/core/aabb.hh>
 #include <nv/interface/mesh_data.hh>
 
@@ -22,4 +23,6 @@
 			initialize();
 		}
+
+		aabb calculate_aabb();
 
 		// assumes that position and normal is vec3, tangent is vec4
Index: /trunk/nv/gfx/skeleton_instance.hh
===================================================================
--- /trunk/nv/gfx/skeleton_instance.hh	(revision 519)
+++ /trunk/nv/gfx/skeleton_instance.hh	(revision 520)
@@ -10,4 +10,5 @@
 #include <nv/common.hh>
 #include <nv/stl/array.hh>
+#include <nv/core/logging.hh>
 #include <nv/interface/context.hh>
 #include <nv/interface/mesh_data.hh>
@@ -126,5 +127,13 @@
 		}
 
+		void delocalize( const data_node_tree& node_data, const array_view< bool >& mask )
+		{
+			for ( uint32 n = 0; n < node_data.size(); ++n )
+				if ( node_data[n].parent_id == -1 )
+					delocalize_rec( node_data, n, transform(), mask );
+		}
+
 		void delocalize_rec( const data_node_tree& node_data, uint32 id, const transform& parent );
+		void delocalize_rec( const data_node_tree& node_data, uint32 id, const transform& parent, const array_view< bool >& mask );
 
 //	protected:
@@ -194,5 +203,5 @@
 
 
-	protected:
+//	protected:
 
 		dynamic_array< mat4 >      m_matrix;
Index: /trunk/nv/gl/gl_context.hh
===================================================================
--- /trunk/nv/gl/gl_context.hh	(revision 519)
+++ /trunk/nv/gl/gl_context.hh	(revision 520)
@@ -41,4 +41,5 @@
 		virtual void release( vertex_array va );
 		virtual void release( framebuffer f );
+		virtual image_data* dump_image( image_format f, image_data* reuse );
 		virtual const framebuffer_info* get_framebuffer_info( framebuffer f ) const;
 
Index: /trunk/nv/image/miniz.hh
===================================================================
--- /trunk/nv/image/miniz.hh	(revision 519)
+++ /trunk/nv/image/miniz.hh	(revision 520)
@@ -17,5 +17,6 @@
 
 	void *miniz_decompress( const void *source_buf, size_t source_buf_len, size_t *out_len, bool parse_header );
-
+	int miniz_compress( unsigned char *pDest, unsigned long *pDest_len, const unsigned char *pSource, unsigned long source_len, int level );
+	unsigned long miniz_bound( unsigned long source_len );
 
 }
Index: /trunk/nv/image/png_writer.hh
===================================================================
--- /trunk/nv/image/png_writer.hh	(revision 520)
+++ /trunk/nv/image/png_writer.hh	(revision 520)
@@ -0,0 +1,33 @@
+// 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.
+
+/**
+ * @file png_writer.hh
+ * @author Kornel Kisielewicz
+ * @brief png_writer
+ */
+
+#ifndef NV_IMAGE_PNG_WRITER_HH
+#define NV_IMAGE_PNG_WRITER_HH
+
+#include <nv/common.hh>
+#include <nv/stl/stream.hh>
+#include <nv/interface/image_writer.hh>
+
+namespace nv
+{
+
+	class png_writer : public image_writer
+	{
+	public:
+		png_writer() {}
+		virtual bool save( stream& s, image_data* data );
+		virtual ~png_writer() {}
+	};
+
+}
+
+#endif // NV_IMAGE_PNG_WRITER_HH
Index: /trunk/nv/interface/context.hh
===================================================================
--- /trunk/nv/interface/context.hh	(revision 519)
+++ /trunk/nv/interface/context.hh	(revision 520)
@@ -159,4 +159,6 @@
 		virtual void release( texture t ) { m_device->release( t ); }
 		virtual void release( buffer b ) { m_device->release( b ); }
+
+		virtual image_data* dump_image( image_format f, image_data* reuse ) = 0;
 
 		virtual texture create_texture( texture_type type, ivec2 size, image_format aformat, sampler asampler, const void* data = nullptr ) = 0;
Index: /trunk/nv/interface/image_writer.hh
===================================================================
--- /trunk/nv/interface/image_writer.hh	(revision 520)
+++ /trunk/nv/interface/image_writer.hh	(revision 520)
@@ -0,0 +1,32 @@
+// 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.
+
+/**
+ * @file image_writer.hh
+ * @author Kornel Kisielewicz
+ * @brief image_writer
+ */
+
+#ifndef NV_INTERFACE_IMAGE_WRITER_HH
+#define NV_INTERFACE_IMAGE_WRITER_HH
+
+#include <nv/common.hh>
+#include <nv/stl/stream.hh>
+#include <nv/interface/image_data.hh>
+
+namespace nv
+{
+
+	class image_writer
+	{
+	public:
+		virtual bool save( stream&, image_data* data ) = 0;
+		virtual ~image_writer() {}
+	};
+
+}
+
+#endif // NV_INTERFACE_IMAGE_WRITER_HH
Index: /trunk/nv/interface/physics_world.hh
===================================================================
--- /trunk/nv/interface/physics_world.hh	(revision 520)
+++ /trunk/nv/interface/physics_world.hh	(revision 520)
@@ -0,0 +1,100 @@
+// Copyright (C) 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 physics_world.hh
+ * @author Kornel Kisielewicz
+ * @brief physics_world
+ */
+
+#ifndef NV_PHYSICS_WORLD_HH
+#define NV_PHYSICS_WORLD_HH
+
+#include <nv/common.hh>
+#include <nv/stl/math.hh>
+#include <nv/core/transform.hh>
+#include <nv/stl/memory.hh>
+
+namespace nv
+{
+	class collision_shape
+	{
+	public:
+		explicit collision_shape( void* p = nullptr, void* m = nullptr ) : internal( p ), mesh( m ) {}
+		void* internal = nullptr;
+		void* mesh     = nullptr;
+	};
+
+
+	class rigid_body
+	{
+	public:
+		explicit rigid_body( void* p = nullptr ) : internal( p ) {}
+		void* internal = nullptr;
+	};
+
+	class constraint
+	{
+	public:
+		explicit constraint( void* p = nullptr ) : internal( p ) {}
+		void* internal = nullptr;
+	};
+
+	enum collision_flags
+	{
+		PCF_STATIC_OBJECT = 1,
+		PCF_KINEMATIC_OBJECT = 2,
+		PCF_NO_CONTACT_RESPONSE = 4,
+		PCF_CUSTOM_MATERIAL_CALLBACK = 8,
+		PCF_CHARACTER_OBJECT = 16,
+		PCF_DISABLE_VISUALIZE_OBJECT = 32,
+		PCF_DISABLE_SPU_COLLISION_PROCESSING = 64
+	};
+
+	enum activation_state
+	{
+		PAS_ACTIVE_TAG = 1,
+		PAS_ISLAND_SLEEPING = 2,
+		PAS_WANTS_DEACTIVATION = 3,
+		PAS_DISABLE_DEACTIVATION = 4,
+		PAS_DISABLE_SIMULATION = 5,
+	};
+
+	class physics_world
+	{
+	public:
+		virtual int step_simulation( float dtime, int max_sub_steps = 1, float fixed_time_step = 1.0f / 60.0f ) = 0;
+		virtual collision_shape create_mesh( array_view< vec3 > vtx, array_view< int > idx ) = 0;
+		virtual collision_shape create_sphere( float radius ) = 0;
+		virtual collision_shape create_capsule( float radius, float height ) = 0;
+		virtual collision_shape create_box( const vec3& half_extens ) = 0;
+		virtual collision_shape create_static_plane( const vec3& norm, float cst ) = 0;
+		virtual rigid_body create_rigid_body( float mass, const transform& tr, collision_shape shape, const vec3& com_offset = vec3() ) = 0;
+		virtual constraint create_hinge_constraint( rigid_body a, const transform& ta, rigid_body b, const transform& tb, const vec2& low_high, const vec3& params = vec3( 0.9f, 0.3f, 1.0f ) ) = 0;
+		virtual constraint create_cone_twist_constraint( rigid_body a, const transform& ta, rigid_body b, const transform& tb, const vec3& sst, const vec3& params = vec3( 1.0f, 0.3f, 1.0f ) ) = 0;
+		virtual void set_gravity( const vec3& ) = 0;
+		virtual void add_rigid_body( rigid_body body ) = 0;
+		virtual void remove_rigid_body( rigid_body body ) = 0;
+		virtual void add_constraint( constraint cons ) = 0;
+		virtual void set_rigid_body_damping( rigid_body body, float linear, float angular ) = 0;
+		virtual void set_rigid_body_deactivation_time( rigid_body body, float time ) = 0;
+		virtual void set_rigid_body_activation_state( rigid_body body, activation_state state ) = 0;
+		virtual void set_rigid_body_sleeping_thresholds( rigid_body body, float linear, float angular ) = 0;
+		virtual void set_rigid_body_linear_velocity( rigid_body body, const vec3& velocity ) = 0;
+		virtual void set_rigid_body_ccd( rigid_body body, float radius, float threshold ) = 0;
+		virtual transform get_world_transform( rigid_body body ) = 0;
+		virtual void set_world_transform( rigid_body body, const transform& tr ) = 0;
+		virtual int get_collision_flags( rigid_body body ) = 0;
+		virtual void set_collision_flags( rigid_body body, int threshold ) = 0;
+		virtual bool ray_test( const vec3& from, const vec3& to, vec3& hpos, vec3& hnorm, bool static_only = false ) = 0;
+		virtual void release( collision_shape ) = 0;
+		virtual void release( rigid_body ) = 0;
+		virtual void release( constraint ) = 0;
+	};
+
+}
+
+#endif // NV_PHYSICS_WORLD_HH
Index: /trunk/nv/lua/lua_aux.hh
===================================================================
--- /trunk/nv/lua/lua_aux.hh	(revision 519)
+++ /trunk/nv/lua/lua_aux.hh	(revision 520)
@@ -9,4 +9,5 @@
 
 #include <nv/lua/lua_state.hh>
+#include <nv/core/random.hh>
 
 namespace nv
@@ -14,4 +15,5 @@
 	namespace lua
 	{
+		random& rng();
 		void register_aux( lua::state* state );
 	}
Index: /trunk/nv/stl/container/random_access.hh
===================================================================
--- /trunk/nv/stl/container/random_access.hh	(revision 519)
+++ /trunk/nv/stl/container/random_access.hh	(revision 520)
@@ -99,5 +99,5 @@
 		inline void fill( const value_type& value )
 		{
-			fill_n( iterator( Super::data() ), iterator( Super::data() + this->size() ), value );
+			::nv::fill( iterator( Super::data() ), iterator( Super::data() + this->size() ), value );
 		}
 
Index: /trunk/nv_bullet.lua
===================================================================
--- /trunk/nv_bullet.lua	(revision 520)
+++ /trunk/nv_bullet.lua	(revision 520)
@@ -0,0 +1,35 @@
+local NV_BT_SFX = ""
+if NV_RUNTIME == "dcrt" then
+	NV_BT_SFX = "_dcrt"
+end
+
+function nv_bullet_configure( dir, subdir, suffix )
+	suffix = suffix or ""
+	links 
+	{
+		"BulletCollision"..suffix..NV_BT_SFX,
+		"BulletDynamics"..suffix..NV_BT_SFX,
+		"BulletSoftBody"..suffix..NV_BT_SFX,
+		"LinearMath"..suffix..NV_BT_SFX,
+	}
+	libdirs { dir.."lib/"..subdir.."/" }
+	includedirs { dir.."src/" }
+end
+
+project "nv-bullet"
+	location (_ACTION.."/"..NV_RUNTIME)
+	language "C++"
+	kind "StaticLib"
+	files { "nv/bullet/**.hh", "src/bullet/**.cc" }
+	includedirs { 
+		"../nv"
+	}
+	links { "nv-core", "nv-gl", "nv-formats", "nv-lua", "nv-lib", "nv-io", "nv-gfx" }
+
+	configuration "debug"
+		nv_bullet_configure( "D:/Libraries/bullet2/", "Debug", "_debug" )
+
+	configuration "not debug"
+		nv_bullet_configure( "D:/Libraries/bullet2/", "Release" )
+
+
Index: /trunk/nv_wx.lua
===================================================================
--- /trunk/nv_wx.lua	(revision 519)
+++ /trunk/nv_wx.lua	(revision 520)
@@ -1,3 +1,11 @@
 assert( NV_RUNTIME == "dcrt" )
+
+project "*"
+	includedirs { 
+		"D:/Libraries/wxwidgets/include/msvc/",
+		"D:/Libraries/wxwidgets/include/",
+	}
+	libdirs { "D:/Libraries/wxwidgets/lib/vc140_dll" }
+	defines { "__WXMSW__", "_UNICODE", "WXUSINGDLL", "wxMSVC_VERSION_AUTO" }
 
 project "nv-wx"
@@ -11,9 +19,2 @@
 	links { "nv-core", "nv-gl", "nv-formats", "nv-lua", "nv-lib", "nv-io", "nv-gfx", "nv-sdl" }
 
-solution "*"
-	includedirs { 
-		"D:/Libraries/wxwidgets/include/msvc/",
-		"D:/Libraries/wxwidgets/include/",
-	}
-	libdirs { "D:/Libraries/wxwidgets/lib/vc140_dll" }
-	defines { "__WXMSW__", "_UNICODE", "WXUSINGDLL", "wxMSVC_VERSION_AUTO" }
Index: /trunk/src/bullet/bullet_helper.hh
===================================================================
--- /trunk/src/bullet/bullet_helper.hh	(revision 520)
+++ /trunk/src/bullet/bullet_helper.hh	(revision 520)
@@ -0,0 +1,56 @@
+// Copyright (C) 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 bullet_helper.hh
+ * @author Kornel Kisielewicz
+ * @brief bullet helper functions
+ */
+
+#ifndef NV_BULLET_HELPER_HH
+#define NV_BULLET_HELPER_HH
+
+#include <nv/common.hh>
+#include <nv/stl/math.hh>
+#include <nv/core/transform.hh>
+#include <LinearMath/btVector3.h>
+#include <LinearMath/btQuaternion.h>
+#include <LinearMath/btTransform.h>
+
+namespace nv
+{
+	btVector3 n2b( vec3 v )
+	{
+		return btVector3( v.x, v.y, v.z );
+	}
+
+	vec3 b2n( const btVector3& v )
+	{
+		return vec3( v.getX(), v.getY(), v.getZ() );
+	}
+
+	btQuaternion n2b( quat v )
+	{
+		return btQuaternion( v.x, v.y, v.z, v.w );
+	}
+
+	quat b2n( const btQuaternion& v )
+	{
+		return quat( v.getW(), v.getX(), v.getY(), v.getZ() );
+	}
+
+	btTransform n2b( transform tr )
+	{
+		return btTransform( n2b( tr.get_orientation() ), n2b( tr.get_position() ) );
+	}
+
+	transform b2n( const btTransform& v )
+	{
+		return transform( b2n( v.getOrigin() ), b2n( v.getRotation() ) );
+	}
+}
+
+#endif // NV_BULLET_HELPER_HH
Index: /trunk/src/bullet/bullet_world.cc
===================================================================
--- /trunk/src/bullet/bullet_world.cc	(revision 520)
+++ /trunk/src/bullet/bullet_world.cc	(revision 520)
@@ -0,0 +1,301 @@
+// Copyright (C) 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/bullet/bullet_world.hh"
+
+#include "src/bullet/bullet_helper.hh"
+#include <btBulletDynamicsCommon.h>
+#include <btBulletCollisionCommon.h>
+
+using namespace nv;
+
+struct alignas( 16 ) motion_state : public btMotionState
+{
+	btTransform m_gfx_world_tr;
+	btTransform	m_com_offset;
+	btTransform	m_com_offset_inv;
+	SIMD_FORCE_INLINE void* operator new( size_t sz ) { return btAlignedAlloc( sz, 16 ); }
+	SIMD_FORCE_INLINE void  operator delete( void* ptr ) { btAlignedFree( ptr ); }
+
+	motion_state( const btTransform& start_tr = btTransform::getIdentity(), const btTransform& com_offset = btTransform::getIdentity() )
+		: m_gfx_world_tr( start_tr )
+		, m_com_offset( com_offset )
+		, m_com_offset_inv( m_com_offset.inverse() )
+	{
+	}
+
+	motion_state( const transform& startTrans = transform(), const nv::transform& com_offset = nv::transform() )
+		: m_gfx_world_tr( n2b( startTrans ) )
+		, m_com_offset( n2b( com_offset ) )
+		, m_com_offset_inv( m_com_offset.inverse() )
+	{
+	}
+
+	nv::transform get_world_transform() const
+	{
+		return b2n( m_gfx_world_tr );
+	}
+
+	void set_world_transform( const nv::transform& tr )
+	{
+		m_gfx_world_tr = n2b( tr );
+	}
+
+	virtual void	getWorldTransform( btTransform& com_world_tr ) const
+	{
+		com_world_tr = m_gfx_world_tr * m_com_offset_inv;
+	}
+
+	virtual void	setWorldTransform( const btTransform& com_world_tr )
+	{
+		m_gfx_world_tr = com_world_tr * m_com_offset;
+	}
+
+};
+
+
+#define SELF static_cast<btDiscreteDynamicsWorld*>( world )
+
+bullet_world::bullet_world()
+{
+	btBroadphaseInterface*               broadphase = new btDbvtBroadphase();
+	btDefaultCollisionConfiguration*     collisionConfiguration = new btDefaultCollisionConfiguration();
+	btCollisionDispatcher*               dispatcher = new btCollisionDispatcher( collisionConfiguration );
+	btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver;
+	btDiscreteDynamicsWorld*             dworld = new btDiscreteDynamicsWorld( dispatcher, broadphase, solver, collisionConfiguration );
+	world = dworld;
+}
+
+void bullet_world::set_gravity( const vec3& gravity )
+{
+	SELF->setGravity( n2b( gravity ) );
+}
+
+int nv::bullet_world::step_simulation( float dtime, int max_sub_steps /*= 1*/, float fixed_time_step /*= 1.0f / 60.0f */ )
+{
+	return SELF->stepSimulation( dtime, max_sub_steps, fixed_time_step );
+}
+
+collision_shape bullet_world::create_sphere( float radius )
+{
+	return collision_shape{ ( void* )new btSphereShape( radius ) };
+}
+
+nv::collision_shape nv::bullet_world::create_capsule( float radius, float height )
+{
+	return collision_shape{ ( void* )new btCapsuleShape( radius, height ) };
+}
+
+collision_shape bullet_world::create_box( const vec3& half_extens )
+{
+	return collision_shape{ ( void* )new btBoxShape( n2b( half_extens ) ) };
+}
+
+collision_shape bullet_world::create_static_plane( const vec3& norm, float cst )
+{
+	return collision_shape{ ( void* )new btStaticPlaneShape( n2b( norm ), cst ) };
+}
+
+void bullet_world::add_rigid_body( rigid_body body )
+{
+	SELF->addRigidBody( static_cast<btRigidBody*>( body.internal ) );
+}
+
+rigid_body bullet_world::create_rigid_body( float mass, const transform& tr, collision_shape shape, const vec3& com_offset )
+{
+	btCollisionShape* sh = static_cast<btCollisionShape*>( shape.internal );
+	bool is_dynamic = ( mass != 0.f );
+
+	btVector3 local_inertia( 0, 0, 0 );
+	if ( is_dynamic )
+		sh->calculateLocalInertia( mass, local_inertia );
+
+	motion_state* ms = new motion_state( tr, nv::transform( com_offset ) );
+
+	btRigidBody::btRigidBodyConstructionInfo rb_info( mass, ms, sh, local_inertia );
+	btRigidBody* body = new btRigidBody( rb_info );
+	return rigid_body{ body };
+}
+
+nv::collision_shape nv::bullet_world::create_mesh( array_view< vec3 > vtx, array_view< int > idx )
+{
+	btTriangleMesh* mesh = new btTriangleMesh;
+	for ( uint32 i = 0; i < idx.size() / 3; ++i )
+	{
+		mesh->addTriangle( n2b( vtx[idx[i * 3+0]] ), n2b(vtx[idx[i * 3+1]]), n2b( vtx[idx[i * 3+ 2]] ) );
+	}
+
+//	btTriangleIndexVertexArray* array = new btTriangleIndexVertexArray( idx.size() / 3, (int*)idx.data(), 3 * sizeof(int), vtx.size(), (float*)vtx.data(), sizeof(vtx) );
+	btBvhTriangleMeshShape* shape = new btBvhTriangleMeshShape( mesh, true );
+//	shape->buildOptimizedBvh();
+	return collision_shape{ shape, nullptr };
+}
+
+constraint nv::bullet_world::create_hinge_constraint( rigid_body a, const transform& ta, rigid_body b, const transform& tb, const vec2& low_high, const vec3& params /*= vec3( 0.9f, 0.3f, 1.0f ) */ )
+{
+	btRigidBody* rba = static_cast<btRigidBody*>( a.internal );
+	btRigidBody* rbb = static_cast<btRigidBody*>( b.internal );
+	btHingeConstraint* hinge = new btHingeConstraint( *rba, *rbb, n2b( ta ), n2b( tb ) );
+	hinge->setLimit( low_high.x, low_high.y, params.x, params.y, params.z );
+	return constraint{ hinge };
+}
+
+constraint nv::bullet_world::create_cone_twist_constraint( rigid_body a, const transform& ta, rigid_body b, const transform& tb, const vec3& sst, const vec3& params /*= vec3( 1.0f, 0.3f, 1.0f ) */ )
+{
+	btRigidBody* rba = static_cast<btRigidBody*>( a.internal );
+	btRigidBody* rbb = static_cast<btRigidBody*>( b.internal );
+	btConeTwistConstraint* cone_twist = new btConeTwistConstraint( *rba, *rbb, n2b( ta ), n2b( tb ) );
+	cone_twist->setLimit( sst.x, sst.y, sst.z, params.x, params.y, params.z );
+	return constraint{ cone_twist };
+}
+
+void nv::bullet_world::remove_rigid_body( rigid_body body )
+{
+	SELF->removeRigidBody( static_cast<btRigidBody*>( body.internal ) );
+}
+
+void nv::bullet_world::add_constraint( constraint cons )
+{
+	SELF->addConstraint( static_cast<btTypedConstraint*>( cons.internal ), true );
+}
+
+void nv::bullet_world::set_rigid_body_damping( rigid_body body, float linear, float angular )
+{
+	btRigidBody* rb = static_cast<btRigidBody*>( body.internal );
+	rb->setDamping( linear, angular );
+}
+
+void nv::bullet_world::set_rigid_body_deactivation_time( rigid_body body, float time )
+{
+	btRigidBody* rb = static_cast<btRigidBody*>( body.internal );
+	rb->setDeactivationTime( time );
+}
+
+void nv::bullet_world::set_rigid_body_activation_state( rigid_body body, activation_state state )
+{
+	btRigidBody* rb = static_cast<btRigidBody*>( body.internal );
+	rb->setActivationState( state );
+}
+
+void nv::bullet_world::set_rigid_body_sleeping_thresholds( rigid_body body, float linear, float angular )
+{
+	btRigidBody* rb = static_cast<btRigidBody*>( body.internal );
+	rb->setSleepingThresholds( linear, angular );
+}
+
+void nv::bullet_world::set_rigid_body_linear_velocity( rigid_body body, const vec3& velocity )
+{
+	btRigidBody* rb = static_cast<btRigidBody*>( body.internal );
+	rb->setLinearVelocity( n2b( velocity ) );
+}
+
+void nv::bullet_world::set_rigid_body_ccd( rigid_body body, float radius, float threshold )
+{
+	btRigidBody* rb = static_cast<btRigidBody*>( body.internal );
+	rb->setCcdMotionThreshold( threshold );
+	rb->setCcdSweptSphereRadius( radius );
+}
+
+nv::transform nv::bullet_world::get_world_transform( rigid_body body )
+{
+	btRigidBody* rb = static_cast<btRigidBody*>( body.internal );
+	return static_cast<motion_state*>( rb->getMotionState() )->get_world_transform();
+}
+
+void nv::bullet_world::set_world_transform( rigid_body body, const nv::transform& tr )
+{
+	btRigidBody* rb = static_cast<btRigidBody*>( body.internal );
+	static_cast<motion_state*>( rb->getMotionState() )->set_world_transform( tr );
+}
+
+int nv::bullet_world::get_collision_flags( rigid_body body )
+{
+	btRigidBody* rb = static_cast<btRigidBody*>( body.internal );
+	return rb->getCollisionFlags();
+}
+
+void nv::bullet_world::set_collision_flags( rigid_body body, int flags )
+{
+	btRigidBody* rb = static_cast<btRigidBody*>( body.internal );
+	return rb->setCollisionFlags( flags );
+}
+
+class ClosestStaticRayResultCallback : public btCollisionWorld::ClosestRayResultCallback
+{
+public:
+	ClosestStaticRayResultCallback( const btVector3& from, const btVector3&  to ) : btCollisionWorld::ClosestRayResultCallback( from, to ) {}
+
+	virtual btScalar addSingleResult( btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace )
+	{
+		if ( !rayResult.m_collisionObject->isStaticObject() )
+			return 1.0;
+
+		return ClosestRayResultCallback::addSingleResult( rayResult, normalInWorldSpace );
+	}
+};
+
+
+bool nv::bullet_world::ray_test( const vec3& from, const vec3& to, vec3& hpos, vec3& hnorm, bool static_only )
+{
+	btVector3 bfrom( n2b( from ) );
+	btVector3 bto( n2b( to ) );
+	if ( !static_only )
+	{
+		btCollisionWorld::ClosestRayResultCallback raycb( bfrom, bto );
+		SELF->rayTest( bfrom, bto, raycb );
+		bool result = false;
+		if ( raycb.hasHit() )
+		{
+			result = true;
+			hpos = b2n( raycb.m_hitPointWorld );
+			hnorm = b2n( raycb.m_hitNormalWorld );
+		}
+		return result;
+	}
+	else
+	{
+		ClosestStaticRayResultCallback raycb( bfrom, bto );
+		SELF->rayTest( bfrom, bto, raycb );
+		bool result = false;
+		if ( raycb.hasHit() )
+		{
+			result = true;
+			hpos = b2n( raycb.m_hitPointWorld );
+			hnorm = b2n( raycb.m_hitNormalWorld );
+		}
+		return result;
+	}
+}
+
+void bullet_world::release( collision_shape sh )
+{
+	if ( !sh.internal ) return;
+	if ( sh.mesh );
+	{
+		btStridingMeshInterface* smi = static_cast<btStridingMeshInterface*>( sh.mesh );
+		delete smi;
+	}
+
+	btCollisionShape* cs = static_cast<btCollisionShape*>( sh.internal );
+	delete cs;
+
+}
+
+void bullet_world::release( constraint c )
+{
+	if ( !c.internal ) return;
+	btTypedConstraint* tc = static_cast<btTypedConstraint*>( c.internal );
+	SELF->removeConstraint( tc );
+	delete tc;
+}
+
+void bullet_world::release( rigid_body body )
+{
+	if ( !body.internal ) return;
+	btRigidBody* rb = static_cast<btRigidBody*>( body.internal );
+	SELF->removeRigidBody( rb );
+	delete rb;
+}
Index: /trunk/src/core/library.cc
===================================================================
--- /trunk/src/core/library.cc	(revision 519)
+++ /trunk/src/core/library.cc	(revision 520)
@@ -11,5 +11,5 @@
 #   include <windows.h>
 #   define NV_LIB_EXT ".dll"
-#   define NV_LIB_OPEN( name ) static_cast<void*>( LoadLibraryEx( name, NULL, LOAD_WITH_ALTERED_SEARCH_PATH ) )
+#   define NV_LIB_OPEN( name ) static_cast<void*>( LoadLibraryExA( name, NULL, LOAD_WITH_ALTERED_SEARCH_PATH ) )
 #   define NV_LIB_GET( handle, name ) reinterpret_cast<void*>( GetProcAddress( static_cast<HMODULE>( handle ), name ) )
 #   define NV_LIB_CLOSE( handle ) ( FreeLibrary( static_cast<HMODULE>( handle ) ) != 0 )
Index: /trunk/src/curses/curses_terminal.cc
===================================================================
--- /trunk/src/curses/curses_terminal.cc	(revision 519)
+++ /trunk/src/curses/curses_terminal.cc	(revision 520)
@@ -131,4 +131,10 @@
 	}
 
+	// if result is a typable char from the 0..9 range
+	if ( result >= 0x108 && result <= 0x108 + 12 )
+	{
+		kevent.key.code = static_cast<nv::key_code>( nv::KEY_F1 + result - 0x108 - 1 );
+	}
+
 	// other recognized codes
 	switch ( result )
Index: /trunk/src/engine/default_resource_manager.cc
===================================================================
--- /trunk/src/engine/default_resource_manager.cc	(revision 519)
+++ /trunk/src/engine/default_resource_manager.cc	(revision 520)
@@ -11,18 +11,19 @@
 default_resource_manager::default_resource_manager( context* context, bool clear_material_paths )
 {
-	m_images        = register_resource_handler< image_data >( new image_manager );
-	m_meshes        = register_resource_handler< data_channel_set >( new mesh_manager );
-	m_binds         = register_resource_handler< animator_bind_data >( new animator_bind_manager );
-	m_animators     = register_resource_handler< animator_data >( new animator_manager );
-	m_materials     = register_resource_handler< material >( new material_manager( clear_material_paths ) );
-	m_programs      = register_resource_handler< program >( new program_manager( context ) );
-	m_gpu_meshes    = register_resource_handler< gpu_mesh >( new gpu_mesh_manager( context, m_meshes ) );
-	m_mesh_datas    = register_resource_handler< mesh_data >( new mesh_data_manager( m_meshes ) );
+	m_images = register_resource_handler< image_data >( new image_manager );
+	m_meshes = register_resource_handler< data_channel_set >( new mesh_manager );
+	m_binds = register_resource_handler< animator_bind_data >( new animator_bind_manager );
+	m_animators = register_resource_handler< animator_data >( new animator_manager );
+	m_materials = register_resource_handler< material >( new material_manager( clear_material_paths ) );
+	m_programs = register_resource_handler< program >( new program_manager( context ) );
+	m_gpu_meshes = register_resource_handler< gpu_mesh >( new gpu_mesh_manager( context, m_meshes ) );
+	m_mesh_datas = register_resource_handler< mesh_data >( new mesh_data_manager( m_meshes ) );
 	m_gpu_materials = register_resource_handler< gpu_material >( new gpu_material_manager( context, m_materials, m_images ) );
-	m_models        = register_resource_handler< model >( new model_manager( this, m_binds, m_mesh_datas ) );
-	m_particles     = register_resource_handler< particle_system_data >( new particle_manager );
+	m_models = register_resource_handler< model >( new model_manager( this, m_binds, m_mesh_datas ) );
+	m_particles = register_resource_handler< particle_system_data >( new particle_manager );
+	m_ragdolls = register_resource_handler< ragdoll_data >( new ragdoll_manager( m_models ) );
 }
 
-void default_resource_manager::initialize( lua::state* lua )
+void default_resource_manager::initialize( lua::state* lua, physics_world* world )
 {
 	m_lua = lua;
@@ -72,4 +73,6 @@
 	m_models->initialize( lua );
 	m_particles->initialize( lua );
+	m_ragdolls->initialize( lua );
+	m_ragdolls->initialize( world );
 }
 
Index: /trunk/src/engine/particle_engine.cc
===================================================================
--- /trunk/src/engine/particle_engine.cc	(revision 519)
+++ /trunk/src/engine/particle_engine.cc	(revision 520)
@@ -1,3 +1,3 @@
-// Copyright (C) 2014-2015 ChaosForge Ltd
+// Copyright (C) 2014-2016 ChaosForge Ltd
 // http://chaosforge.org/
 //
@@ -26,5 +26,5 @@
 using namespace nv;
 
-static void nv_particle_emitter_point( const particle_emitter_data*, particle* p, uint32 count )
+static void nv_particle_emitter_point( random_base*, const particle_emitter_data*, particle* p, uint32 count )
 {
 	for ( uint32 i = 0; i < count; ++i )
@@ -34,35 +34,32 @@
 }
 
-static void nv_particle_emitter_box( const particle_emitter_data* pe, particle* p, uint32 count )
-{
-	random& r = random::get();
-	for ( uint32 i = 0; i < count; ++i )
-	{
-		p[i].position = 
-			r.frange( -pe->hextents[0], pe->hextents[0] ) * pe->cdir +
-			r.frange( 0.0f, pe->extents[1] ) * pe->dir +
-			r.frange( -pe->hextents[2], pe->hextents[2] ) * pe->odir;
-	}
-}
-
-static void nv_particle_emitter_cylinder( const particle_emitter_data* pe, particle* p, uint32 count )
-{
-	random& r = random::get();
-	for ( uint32 i = 0; i < count; ++i )
-	{
-		vec2 rellipse( r.disk_point( pe->precise ) * pe->extents[0] );
+static void nv_particle_emitter_box( random_base* r, const particle_emitter_data* pe, particle* p, uint32 count )
+{
+	for ( uint32 i = 0; i < count; ++i )
+	{
+		p[i].position = 
+			r->frange( -pe->hextents[0], pe->hextents[0] ) * pe->cdir +
+			r->frange( 0.0f, pe->extents[1] ) * pe->dir +
+			r->frange( -pe->hextents[2], pe->hextents[2] ) * pe->odir;
+	}
+}
+
+static void nv_particle_emitter_cylinder( random_base* r, const particle_emitter_data* pe, particle* p, uint32 count )
+{
+	for ( uint32 i = 0; i < count; ++i )
+	{
+		vec2 rellipse( r->disk_point( pe->precise ) * pe->extents[0] );
 		p[i].position = 
 			rellipse.x * pe->cdir +
-			r.frange( 0.0f, pe->extents[1] ) * pe->dir +
+			r->frange( 0.0f, pe->extents[1] ) * pe->dir +
 			rellipse.y * pe->odir;
 	}
 }
 
-static void nv_particle_emitter_sphere( const particle_emitter_data* pe, particle* p, uint32 count )
-{
-	random& r = random::get();
-	for ( uint32 i = 0; i < count; ++i )
-	{
-		vec3 rsphere = r.sphere_point( pe->precise ) * pe->extents[0];
+static void nv_particle_emitter_sphere( random_base* r, const particle_emitter_data* pe, particle* p, uint32 count )
+{
+	for ( uint32 i = 0; i < count; ++i )
+	{
+		vec3 rsphere = r->sphere_point( pe->precise ) * pe->extents[0];
 		p[i].position = 
 			rsphere.x * pe->cdir +
@@ -72,23 +69,21 @@
 }
 
-static void nv_particle_emitter_cylindroid( const particle_emitter_data* pe, particle* p, uint32 count )
-{
-	random& r = random::get();
-	for ( uint32 i = 0; i < count; ++i )
-	{
-		vec2 rellipse = r.ellipse_point( vec2( pe->hextents[0], pe->hextents[2] ), pe->precise );
+static void nv_particle_emitter_cylindroid( random_base* r, const particle_emitter_data* pe, particle* p, uint32 count )
+{
+	for ( uint32 i = 0; i < count; ++i )
+	{
+		vec2 rellipse = r->ellipse_point( vec2( pe->hextents[0], pe->hextents[2] ), pe->precise );
 		p[i].position = 
 			rellipse.x * pe->cdir +
-			r.frange( 0.0f, pe->extents[1] ) * pe->dir +
+			r->frange( 0.0f, pe->extents[1] ) * pe->dir +
 			rellipse.y * pe->odir;
 	}
 }
 
-static void nv_particle_emitter_ellipsoid( const particle_emitter_data* pe, particle* p, uint32 count )
-{
-	random& r = random::get();
-	for ( uint32 i = 0; i < count; ++i )
-	{
-		vec3 rsphere = r.ellipsoid_point( pe->hextents, pe->precise );
+static void nv_particle_emitter_ellipsoid( random_base* r, const particle_emitter_data* pe, particle* p, uint32 count )
+{
+	for ( uint32 i = 0; i < count; ++i )
+	{
+		vec3 rsphere = r->ellipsoid_point( pe->hextents, pe->precise );
 		p[i].position = 
 			rsphere.x * pe->cdir +
@@ -98,10 +93,9 @@
 }
 
-static void nv_particle_emitter_hollow_cylinder( const particle_emitter_data* pe, particle* p, uint32 count )
-{
-	random& r = random::get();
-	for ( uint32 i = 0; i < count; ++i )
-	{
-		vec2 rellipse = r.hollow_disk_point( 
+static void nv_particle_emitter_hollow_cylinder( random_base* r, const particle_emitter_data* pe, particle* p, uint32 count )
+{
+	for ( uint32 i = 0; i < count; ++i )
+	{
+		vec2 rellipse = r->hollow_disk_point(
 			pe->ihextents[0],
 			pe->hextents[0],
@@ -109,15 +103,14 @@
 		p[i].position = 
 			rellipse.x * pe->cdir +
-			r.frange( 0.0f, pe->extents[1] ) * pe->dir +
+			r->frange( 0.0f, pe->extents[1] ) * pe->dir +
 			rellipse.y * pe->odir;
 	}
 }
 
-static void nv_particle_emitter_hollow_sphere( const particle_emitter_data* pe, particle* p, uint32 count )
-{
-	random& r = random::get();
-	for ( uint32 i = 0; i < count; ++i )
-	{
-		vec3 rellipse = r.hollow_sphere_point( pe->ihextents[0], pe->hextents[0], pe->precise );
+static void nv_particle_emitter_hollow_sphere( random_base* r, const particle_emitter_data* pe, particle* p, uint32 count )
+{
+	for ( uint32 i = 0; i < count; ++i )
+	{
+		vec3 rellipse = r->hollow_sphere_point( pe->ihextents[0], pe->hextents[0], pe->precise );
 		p[i].position = 
 			rellipse.x * pe->cdir +
@@ -127,10 +120,9 @@
 }
 
-static void nv_particle_emitter_hollow_cylindroid( const particle_emitter_data* pe, particle* p, uint32 count )
-{
-	random& r = random::get();
-	for ( uint32 i = 0; i < count; ++i )
-	{
-		vec2 rellipse = r.hollow_ellipse_point( 
+static void nv_particle_emitter_hollow_cylindroid( random_base* r, const particle_emitter_data* pe, particle* p, uint32 count )
+{
+	for ( uint32 i = 0; i < count; ++i )
+	{
+		vec2 rellipse = r->hollow_ellipse_point(
 			vec2( pe->ihextents[0], pe->ihextents[2] ), 
 			vec2( pe->hextents[0], pe->hextents[2] ), 
@@ -138,15 +130,14 @@
 		p[i].position = 
 			rellipse.x * pe->cdir +
-			r.frange( 0.0f, pe->extents[1] ) * pe->dir +
+			r->frange( 0.0f, pe->extents[1] ) * pe->dir +
 			rellipse.y * pe->odir;
 	}
 }
 
-static void nv_particle_emitter_hollow_ellipsoid( const particle_emitter_data* pe, particle* p, uint32 count )
-{
-	random& r = random::get();
-	for ( uint32 i = 0; i < count; ++i )
-	{
-		vec3 rellipse = r.hollow_ellipsoid_point( pe->ihextents, pe->hextents, pe->precise );
+static void nv_particle_emitter_hollow_ellipsoid( random_base* r, const particle_emitter_data* pe, particle* p, uint32 count )
+{
+	for ( uint32 i = 0; i < count; ++i )
+	{
+		vec3 rellipse = r->hollow_ellipsoid_point( pe->ihextents, pe->hextents, pe->precise );
 		p[i].position = 
 			rellipse.x * pe->cdir +
@@ -278,12 +269,16 @@
 }
 
-nv::particle_engine::particle_engine( context* a_context, bool debug_data )
-{
-	m_context       = a_context;
+nv::particle_engine::particle_engine( context* a_context, random_base* rng, particle_group_manager* groups, bool debug_data )
+{
+	m_context = a_context;
+	m_pgm     = groups;
+	m_rng     = rng;
+	if ( m_rng == nullptr )
+		m_rng = &random::get();
+
 	if ( debug_data )
 	{
 		m_debug_emitter_names  = new nv::hash_map< nv::particle_emitter_func,  nv::const_string >;
 		m_debug_affector_names = new nv::hash_map< nv::particle_affector_func, nv::const_string >;
-		m_debug_affector_types = new nv::hash_map< nv::particle_affector_func, nv::type_entry* >;
 		m_debug_emitter_list   = new nv::vector< nv::particle_emitter_func >;
 		m_debug_affector_list  = new nv::vector< nv::particle_affector_func >;
@@ -294,9 +289,9 @@
 }
 
-nv::particle_system nv::particle_engine::create_system( const particle_system_data* data, particle_system_group group )
-{
-	particle_system_group_info* ginfo = m_groups.get( group );
-	if ( !ginfo ) return nv::particle_system();
-
+nv::particle_system nv::particle_engine::create_system( const particle_system_data* data, particle_group group )
+{
+	if ( !m_pgm->ref( group ) ) 
+		return nv::particle_system();
+	
 	particle_system result = m_systems.create();
 	particle_system_info* info = m_systems.get( result );
@@ -310,5 +305,5 @@
 			info->emitters[i].pause = 0;
 		else
-			info->emitters[i].pause = random::get().frange( data->emitters[i].duration_min, data->emitters[i].duration_max );
+			info->emitters[i].pause = m_rng->frange( data->emitters[i].duration_min, data->emitters[i].duration_max );
 
 	}
@@ -316,10 +311,9 @@
 	info->count = 0;
 	info->particles = new particle[data->quota];
-	ginfo->ref_counter++;
 
 	return result;
 }
 
-nv::particle_system nv::particle_engine::create_system( resource< nv::particle_system_data > rdata, particle_system_group group )
+nv::particle_system nv::particle_engine::create_system( resource< nv::particle_system_data > rdata, particle_group group )
 {
 	if ( auto data = rdata.lock() )
@@ -328,27 +322,12 @@
 }
 
-void nv::particle_engine::prepare( particle_system_group group )
-{
-	particle_system_group_info* info = m_groups.get( group );
-	if ( info )
-	{
-		info->count = 0;
-	}
-}
-
-nv::particle_system_group nv::particle_engine::create_group( uint32 max_particles )
-{
-	particle_system_group result     = m_groups.create();
-	particle_system_group_info* info = m_groups.get( result );
-	info->local = false;
-	info->count = 0;
-	info->quota = max_particles;
-	info->vtx_buffer = m_context->create_buffer( VERTEX_BUFFER, STREAM_DRAW, info->quota * sizeof( particle_quad )/*, info->quads_[0].data*/ );
-	vertex_array_desc desc;
-	desc.add_vertex_buffers< particle_vtx >( info->vtx_buffer, true );
-	info->vtx_array = m_context->create_vertex_array( desc );
-	info->quads     = new particle_quad[info->quota];
-	info->ref_counter = 0;
-	return result;
+void nv::particle_engine::prepare( particle_group group )
+{
+	m_pgm->prepare( group );
+}
+
+nv::particle_group nv::particle_engine::create_group( uint32 max_particles )
+{
+	return m_pgm->create_group( max_particles );
 }
 
@@ -369,6 +348,6 @@
 	while ( m_systems.size() > 0 )
 		release( m_systems.get_handle( 0 ) );
-	while ( m_groups.size() > 0 )
-		release( m_groups.get_handle( 0 ) );
+	if ( m_pgm )
+		m_pgm->reset();
 	m_emitters.clear();
 	m_affectors.clear();
@@ -381,20 +360,13 @@
 	if ( info )
 	{
-		particle_system_group_info* ginfo = m_groups.get( info->group );
-		if ( ginfo ) ginfo->ref_counter--;
-				delete[] info->particles;
+		m_pgm->unref( info->group );
+		delete[] info->particles;
 		m_systems.destroy( system );
 	}
 }
 
-void nv::particle_engine::release( particle_system_group group )
-{
-	particle_system_group_info* info = m_groups.get( group );
-	if ( info )
-	{
-		delete[] info->quads;
-		m_context->release( info->vtx_array );
-		m_groups.destroy( group );
-	}
+void nv::particle_engine::release( particle_group group )
+{
+	m_pgm->release( group );
 }
 
@@ -403,7 +375,5 @@
 	particle_system_info* info = m_systems.get( system );
 	if ( info )
-	{
-		generate_data( info, s );
-	}
+		m_pgm->generate_data( array_view< particle >( info->particles, info->count ), info->data, info->group, s );
 }
 
@@ -440,126 +410,4 @@
 		info->texcoords[1] = b;
 	}
-}
-
-void nv::particle_engine::generate_data( particle_system_info* info, const scene_state& s )
-{
-//  	void* rawptr = m_context->map_buffer( info->vtx_buffer, nv::WRITE_UNSYNCHRONIZED, offset, info->count*sizeof( particle_quad ) );
-//  	particle_quad* quads = reinterpret_cast<particle_quad*>( rawptr );
-
-	particle_system_group_info* group = m_groups.get( info->group );
-	if ( !info )
-	{
-		return;
-	}
-
-	vec2 lb     = vec2( -0.5f, -0.5f );
-	vec2 rt     = vec2( 0.5f, 0.5f );
-
-	switch ( info->data->origin )
-	{
-	case particle_origin::CENTER        : break;
-	case particle_origin::TOP_LEFT      : lb = vec2(0.f,-1.f); rt = vec2(1.f,0.f);  break;
-	case particle_origin::TOP_CENTER    : lb.y = -1.f; rt.y = 0.f; break;
-	case particle_origin::TOP_RIGHT     : lb = vec2(-1.f,-1.f); rt = vec2(); break;
-	case particle_origin::CENTER_LEFT   : lb.x = 0.f; rt.x = 1.f; break;
-	case particle_origin::CENTER_RIGHT  : lb.x = -1.f; rt.x = 0.f; break;
-	case particle_origin::BOTTOM_LEFT   : lb = vec2(); rt = vec2(1.f,1.f); break;
-	case particle_origin::BOTTOM_CENTER : lb.y = 0.f; rt.y = 1.f; break; 
-	case particle_origin::BOTTOM_RIGHT  : lb = vec2(-1.f,0.f); rt = vec2(.0f,1.f); break;
-	}
-
-	const vec3 sm[4] = 
-	{ 
-		vec3( lb.x, lb.y, 0.0f ),
-		vec3( rt.x, lb.y, 0.0f ),
-		vec3( lb.x, rt.y, 0.0f ),
-		vec3( rt.x, rt.y, 0.0f ),
-	};
-	vec3 z( 0.0f, 0.0f ,1.0f );
-
-	particle_orientation orientation = info->data->orientation;
-	vec3 common_up ( info->data->common_up );
-	vec3 common_dir( info->data->common_dir );
-	bool accurate_facing = info->data->accurate_facing;
-	mat3 rot_mat;
-	vec3 right;
-	vec3 pdir( 0.0f, 1.0f, 0.0f );
-
-	vec3 camera_pos   = s.get_camera().get_position();
-	vec3 inv_view_dir = math::normalize( -s.get_camera().get_direction() );
-
-	for ( uint32 i = 0; i < info->count; ++i )
-	{
-		const particle& pdata = info->particles[i];
-//		particle_quad& rdata  = quads[i];
-		particle_quad& rdata  = group->quads[i + group->count];
-
-		vec3 view_dir( inv_view_dir );
-		if ( accurate_facing ) view_dir = math::normalize( camera_pos - pdata.position );
-
-		switch ( orientation )
-		{
-		case particle_orientation::POINT :
-			right   = math::normalize( math::cross( view_dir, vec3( 0, 1, 0 ) ) );
-			right   = math::rotate( right, pdata.rotation, view_dir );
-			rot_mat = mat3( right, math::cross( right, -view_dir ), -view_dir );
-			break;
-		case particle_orientation::ORIENTED :
-			pdir    = normalize_safe( pdata.velocity, pdir );
-			right   = math::normalize( math::cross( pdir, view_dir ) );
-			rot_mat = mat3( right, pdir, math::cross( pdir, right ) );
-			break;
-		case particle_orientation::ORIENTED_COMMON :
-			right   = math::normalize( math::cross( common_dir, view_dir ) );
-			rot_mat = mat3( right, common_dir, math::cross( common_dir, right ) );
-			break;
-		case particle_orientation::PERPENDICULAR :
-			pdir    = normalize_safe( pdata.velocity, pdir );
-			right   = math::normalize( math::cross( common_up, pdir ) );
-			rot_mat = mat3( right, common_up, math::cross( common_up, right ) );
-			break;
-		case particle_orientation::PERPENDICULAR_COMMON :
-			right   = math::normalize( math::cross( common_up, common_dir ) );
-			rot_mat = mat3( right, common_up, math::cross( common_up, right ) );
-			break;
-		}
-
-		vec2 texcoords[4] =
-		{
-			pdata.tcoord_a,
-			vec2( pdata.tcoord_b.x, pdata.tcoord_a.y ),
-			vec2( pdata.tcoord_a.x, pdata.tcoord_b.y ),
-			pdata.tcoord_b
-		};
-
-		vec3 size( pdata.size.x, pdata.size.y, 0.0f );
-		vec3 s0 = rot_mat * ( ( size * sm[0] ) );
-		vec3 s1 = rot_mat * ( ( size * sm[1] ) );
-		vec3 s2 = rot_mat * ( ( size * sm[2] ) );
-		vec3 s3 = rot_mat * ( ( size * sm[3] ) );
-
-		rdata.data[0].position = pdata.position + s0;
-		rdata.data[0].color    = pdata.color;
-		rdata.data[0].texcoord = texcoords[0];
-
-		rdata.data[1].position = pdata.position + s1;
-		rdata.data[1].color    = pdata.color;
-		rdata.data[1].texcoord = texcoords[1];
-
-		rdata.data[2].position = pdata.position + s2;
-		rdata.data[2].color    = pdata.color;
-		rdata.data[2].texcoord = texcoords[2];
-
-		rdata.data[3].position = pdata.position + s3;
-		rdata.data[3].color    = pdata.color;
-		rdata.data[3].texcoord = texcoords[3];
-
-		rdata.data[4] = rdata.data[2];
-		rdata.data[5] = rdata.data[1];
-	}
-//	m_context->unmap_buffer( info->vtx_buffer );
-
-	m_context->update( group->vtx_buffer, group->quads, group->count*sizeof(particle_quad), info->count*sizeof( particle_quad ) );
-	group->count += info->count;
 }
 
@@ -584,5 +432,4 @@
 	if ( ecount == 0 ) return;
 
-	random& r = random::get();
 	bool local = model.is_identity();
 // 	if ( !local ) 
@@ -605,5 +452,5 @@
 				{
 					particle& pinfo = info->particles[info->count];
-					edata.emitter_func( &(info->data->emitters[i]), &pinfo, 1 );
+					edata.emitter_func( m_rng, &(info->data->emitters[i]), &pinfo, 1 );
 					pinfo.position = vec3();
 					pinfo.position+= edata.position;
@@ -611,14 +458,14 @@
 						pinfo.position = pinfo.position * model;
 					pinfo.color    = edata.color_min == edata.color_max ?
-						edata.color_min : r.range( edata.color_min, edata.color_max );
+						edata.color_min : m_rng->range( edata.color_min, edata.color_max );
 					pinfo.size     = edata.size_min == edata.size_max ?
-						edata.size_min : r.range( edata.size_min, edata.size_max );
+						edata.size_min : m_rng->range( edata.size_min, edata.size_max );
 					if ( edata.square ) pinfo.size.y = pinfo.size.x;
 					float velocity = edata.velocity_min == edata.velocity_max ?
-						edata.velocity_min : r.frange( edata.velocity_min, edata.velocity_max );
+						edata.velocity_min : m_rng->frange( edata.velocity_min, edata.velocity_max );
 					pinfo.lifetime = dtime + einfo.next;
 					pinfo.death = ( edata.lifetime_min == edata.lifetime_max ?
-						edata.lifetime_min : r.frange( edata.lifetime_min, edata.lifetime_max ) );
-					pinfo.rotation = r.frand( 2* math::pi<float>() );
+						edata.lifetime_min : m_rng->frange( edata.lifetime_min, edata.lifetime_max ) );
+					pinfo.rotation = m_rng->frand( 2* math::pi<float>() );
 					pinfo.tcoord_a = info->texcoords[0];
 					pinfo.tcoord_b = info->texcoords[1];
@@ -628,7 +475,7 @@
 					{
 						float emission_angle = math::radians( edata.angle );
-						float cos_theta = r.frange( cos( emission_angle ), 1.0f );
+						float cos_theta = m_rng->frange( cos( emission_angle ), 1.0f );
 						float sin_theta = sqrt(1.0f - cos_theta * cos_theta );
-						float phi       = r.frange( 0.0f, 2* math::pi<float>() );
+						float phi       = m_rng->frange( 0.0f, 2* math::pi<float>() );
 						pinfo.velocity  = model.get_orientation() * 
 							( edata.odir * ( cos(phi) * sin_theta ) +
@@ -672,5 +519,4 @@
 	uint32 ecount = info->data->emitter_count;
 	if ( ecount == 0 ) return;
-	random& r = random::get();
 
 	for ( uint32 i = 0; i < ecount; ++i )
@@ -691,5 +537,5 @@
 				einfo.active = false;
 				if ( edata.repeat_min > 0.0f )
-					einfo.pause += r.frange( edata.repeat_min, edata.repeat_max );
+					einfo.pause += m_rng->frange( edata.repeat_min, edata.repeat_max );
 				else
 					einfo.pause = 0.0f;
@@ -698,5 +544,5 @@
 			{
 				einfo.active = true;
-				einfo.pause += r.frange( edata.duration_min, edata.duration_max );
+				einfo.pause += m_rng->frange( edata.duration_min, edata.duration_max );
 			}
 		}
@@ -776,12 +622,7 @@
 }
 
-nv::particle_render_data nv::particle_engine::get_render_data( particle_system_group group )
-{
-	const particle_system_group_info* info = m_groups.get( group );
-	if ( info )
-	{
-		return{ info->count, info->vtx_array };
-	}
-	return { 0, nv::vertex_array() };
+nv::particle_render_data nv::particle_engine::get_render_data( particle_group group )
+{
+	return m_pgm->get_render_data( group );
 }
 
@@ -794,5 +635,16 @@
 }
 
-void nv::particle_engine::register_types( type_database* db )
+const nv::type_entry* nv::particle_engine::get_debug_type( particle_affector_func f )
+{
+	if ( m_debug_affector_types )
+	{
+		auto it = m_debug_affector_types->find( f );
+		if ( it != m_debug_affector_types->end() )
+			return it->second;
+	}
+	return nullptr;
+}
+
+void nv::particle_engine::register_types( type_database* db, bool debug_data )
 {
 	db->create_type<particle_orientation>()
@@ -816,6 +668,9 @@
 		;
 
-	if ( m_debug_affector_types )
-	{
+	if ( debug_data )
+	{
+		if ( !m_debug_affector_types )
+			m_debug_affector_types = new nv::hash_map< nv::particle_affector_func, nv::type_entry* >;
+
 		int you_could_get_rid_of_the_init_function_using_these; int error;
 		int universal_vm_based_affector;
@@ -849,2 +704,3 @@
 	}
 }
+
Index: /trunk/src/engine/particle_group.cc
===================================================================
--- /trunk/src/engine/particle_group.cc	(revision 520)
+++ /trunk/src/engine/particle_group.cc	(revision 520)
@@ -0,0 +1,223 @@
+// 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/particle_group.hh"
+
+using namespace nv;
+
+particle_group_manager::particle_group_manager( context* a_context )
+	: m_context( a_context )
+{
+
+}
+
+particle_group particle_group_manager::create_group( uint32 max_particles )
+{
+	particle_group result = m_groups.create();
+	particle_group_info* info = m_groups.get( result );
+	info->local = false;
+	info->count = 0;
+	info->quota = max_particles;
+	info->vtx_buffer = m_context->create_buffer( VERTEX_BUFFER, STREAM_DRAW, info->quota * sizeof( particle_quad )/*, info->quads_[0].data*/ );
+	vertex_array_desc desc;
+	desc.add_vertex_buffers< particle_vtx >( info->vtx_buffer, true );
+	info->vtx_array = m_context->create_vertex_array( desc );
+	info->quads = new particle_quad[info->quota];
+	info->ref_counter = 0;
+	return result;
+}
+
+void particle_group_manager::release( particle_group group )
+{
+	particle_group_info* info = m_groups.get( group );
+	if ( info )
+	{
+		delete[] info->quads;
+		m_context->release( info->vtx_array );
+		m_groups.destroy( group );
+	}
+}
+
+void particle_group_manager::prepare( particle_group group )
+{
+	particle_group_info* info = m_groups.get( group );
+	if ( info )
+	{
+		info->count = 0;
+	}
+}
+
+void particle_group_manager::reset()
+{
+	clear();
+}
+
+void particle_group_manager::generate_data(
+	array_view< particle > particles,
+	const particle_group_settings* data,
+	particle_group pgroup,
+	const scene_state& s
+)
+{
+	//  	void* rawptr = m_context->map_buffer( info->vtx_buffer, nv::WRITE_UNSYNCHRONIZED, offset, info->count*sizeof( particle_quad ) );
+	//  	particle_quad* quads = reinterpret_cast<particle_quad*>( rawptr );
+
+	particle_group_info* group = m_groups.get( pgroup );
+
+	if ( particles.size() == 0 || !data || !group )
+	{
+		return;
+	}
+
+	vec2 lb = vec2( -0.5f, -0.5f );
+	vec2 rt = vec2( 0.5f, 0.5f );
+
+	switch ( data->origin )
+	{
+	case particle_origin::CENTER: break;
+	case particle_origin::TOP_LEFT: lb = vec2( 0.f, -1.f ); rt = vec2( 1.f, 0.f );  break;
+	case particle_origin::TOP_CENTER: lb.y = -1.f; rt.y = 0.f; break;
+	case particle_origin::TOP_RIGHT: lb = vec2( -1.f, -1.f ); rt = vec2(); break;
+	case particle_origin::CENTER_LEFT: lb.x = 0.f; rt.x = 1.f; break;
+	case particle_origin::CENTER_RIGHT: lb.x = -1.f; rt.x = 0.f; break;
+	case particle_origin::BOTTOM_LEFT: lb = vec2(); rt = vec2( 1.f, 1.f ); break;
+	case particle_origin::BOTTOM_CENTER: lb.y = 0.f; rt.y = 1.f; break;
+	case particle_origin::BOTTOM_RIGHT: lb = vec2( -1.f, 0.f ); rt = vec2( .0f, 1.f ); break;
+	}
+
+	const vec3 sm[4] =
+	{
+		vec3( lb.x, lb.y, 0.0f ),
+		vec3( rt.x, lb.y, 0.0f ),
+		vec3( lb.x, rt.y, 0.0f ),
+		vec3( rt.x, rt.y, 0.0f ),
+	};
+	vec3 z( 0.0f, 0.0f, 1.0f );
+
+	particle_orientation orientation = data->orientation;
+	vec3 common_up( data->common_up );
+	vec3 common_dir( data->common_dir );
+	bool accurate_facing = data->accurate_facing;
+	mat3 rot_mat;
+	vec3 right;
+	vec3 pdir( 0.0f, 1.0f, 0.0f );
+
+	vec3 camera_pos = s.get_camera().get_position();
+	vec3 inv_view_dir = math::normalize( -s.get_camera().get_direction() );
+
+	for ( uint32 i = 0; i < particles.size(); ++i )
+	{
+		const particle& pdata = particles[i];
+		//		particle_quad& rdata  = quads[i];
+		particle_quad& rdata = group->quads[i + group->count];
+
+		vec3 view_dir( inv_view_dir );
+		if ( accurate_facing ) view_dir = math::normalize( camera_pos - pdata.position );
+
+		switch ( orientation )
+		{
+		case particle_orientation::POINT:
+			right = math::normalize( math::cross( view_dir, vec3( 0, 1, 0 ) ) );
+			right = math::rotate( right, pdata.rotation, view_dir );
+			rot_mat = mat3( right, math::cross( right, -view_dir ), -view_dir );
+			break;
+		case particle_orientation::ORIENTED:
+			pdir = normalize_safe( pdata.velocity, pdir );
+			right = math::normalize( math::cross( pdir, view_dir ) );
+			rot_mat = mat3( right, pdir, math::cross( pdir, right ) );
+			break;
+		case particle_orientation::ORIENTED_COMMON:
+			right = math::normalize( math::cross( common_dir, view_dir ) );
+			rot_mat = mat3( right, common_dir, math::cross( common_dir, right ) );
+			break;
+		case particle_orientation::PERPENDICULAR:
+			pdir = normalize_safe( pdata.velocity, pdir );
+			right = math::normalize( math::cross( common_up, pdir ) );
+			rot_mat = mat3( right, common_up, math::cross( common_up, right ) );
+			break;
+		case particle_orientation::PERPENDICULAR_COMMON:
+			right = math::normalize( math::cross( common_up, common_dir ) );
+			rot_mat = mat3( right, common_up, math::cross( common_up, right ) );
+			break;
+		}
+
+		vec2 texcoords[4] =
+		{
+			pdata.tcoord_a,
+			vec2( pdata.tcoord_b.x, pdata.tcoord_a.y ),
+			vec2( pdata.tcoord_a.x, pdata.tcoord_b.y ),
+			pdata.tcoord_b
+		};
+
+		vec3 size( pdata.size.x, pdata.size.y, 0.0f );
+		vec3 s0 = rot_mat * ( ( size * sm[0] ) );
+		vec3 s1 = rot_mat * ( ( size * sm[1] ) );
+		vec3 s2 = rot_mat * ( ( size * sm[2] ) );
+		vec3 s3 = rot_mat * ( ( size * sm[3] ) );
+
+		rdata.data[0].position = pdata.position + s0;
+		rdata.data[0].color = pdata.color;
+		rdata.data[0].texcoord = texcoords[0];
+
+		rdata.data[1].position = pdata.position + s1;
+		rdata.data[1].color = pdata.color;
+		rdata.data[1].texcoord = texcoords[1];
+
+		rdata.data[2].position = pdata.position + s2;
+		rdata.data[2].color = pdata.color;
+		rdata.data[2].texcoord = texcoords[2];
+
+		rdata.data[3].position = pdata.position + s3;
+		rdata.data[3].color = pdata.color;
+		rdata.data[3].texcoord = texcoords[3];
+
+		rdata.data[4] = rdata.data[2];
+		rdata.data[5] = rdata.data[1];
+	}
+	//	m_context->unmap_buffer( info->vtx_buffer );
+
+	m_context->update( group->vtx_buffer, group->quads, group->count * sizeof( particle_quad ), particles.size() * sizeof( particle_quad ) );
+	group->count += particles.size();
+}
+
+particle_render_data nv::particle_group_manager::get_render_data( particle_group group )
+{
+	const particle_group_info* info = m_groups.get( group );
+	if ( info )
+	{
+		return{ info->count, info->vtx_array };
+	}
+	return{ 0, nv::vertex_array() };
+}
+
+particle_group_manager::~particle_group_manager()
+{
+	clear();
+}
+
+void particle_group_manager::clear()
+{
+	while ( m_groups.size() > 0 )
+		release( m_groups.get_handle( 0 ) );
+}
+
+bool particle_group_manager::ref( particle_group group )
+{
+	particle_group_info* info = m_groups.get( group );
+	if ( !info ) return false;
+	info->ref_counter++;
+	return true;
+}
+
+bool particle_group_manager::unref( particle_group group )
+{
+	particle_group_info* info = m_groups.get( group );
+	if ( !info ) return false;
+	info->ref_counter--;
+	return true;
+}
+
+
Index: /trunk/src/engine/particle_manager.cc
===================================================================
--- /trunk/src/engine/particle_manager.cc	(revision 519)
+++ /trunk/src/engine/particle_manager.cc	(revision 520)
@@ -61,32 +61,6 @@
 	data->emitter_count   = 0;
 	data->affector_count  = 0;
-
-	const_string orientation = table.get_string( "orientation", "point" );
-	if ( orientation == "point" )                     { data->orientation = particle_orientation::POINT; }
-	else if ( orientation == "oriented" )             { data->orientation = particle_orientation::ORIENTED; }
-	else if ( orientation == "oriented_common" )      { data->orientation = particle_orientation::ORIENTED_COMMON; }
-	else if ( orientation == "perpendicular" )        { data->orientation = particle_orientation::PERPENDICULAR; }
-	else if ( orientation == "perpendicular_common" ) { data->orientation = particle_orientation::PERPENDICULAR_COMMON; }
-	else 
-	{
-		NV_LOG_ERROR( "Unknown orientation type! (", orientation, ")!" );
-		data->orientation = particle_orientation::POINT;
-	}
-
-	const_string origin = table.get_string( "origin", "center" );
-	if      ( origin == "center" )        { data->origin = particle_origin::CENTER; }
-	else if ( origin == "top_left" )      { data->origin = particle_origin::TOP_LEFT; }
-	else if ( origin == "top_center" )    { data->origin = particle_origin::TOP_CENTER; }
-	else if ( origin == "top_right" )     { data->origin = particle_origin::TOP_RIGHT; }
-	else if ( origin == "center_left" )   { data->origin = particle_origin::CENTER_LEFT; }
-	else if ( origin == "center_right" )  { data->origin = particle_origin::CENTER_RIGHT; }
-	else if ( origin == "bottom_left" )   { data->origin = particle_origin::BOTTOM_LEFT; }
-	else if ( origin == "bottom_center" ) { data->origin = particle_origin::BOTTOM_CENTER; }
-	else if ( origin == "bottom_right" )  { data->origin = particle_origin::BOTTOM_RIGHT; }
-	else 
-	{
-		NV_LOG_ERROR( "Unknown particle origin! (", origin, ")!" );
-		data->origin = particle_origin::CENTER;
-	}
+	data->orientation     = particle_orientation( table.get_unsigned("orientation", 0) );
+	data->origin          = particle_origin( table.get_unsigned( "origin",0  ) );
 
 	data->common_up  = math::normalize( table.get<vec3>("common_up",  vec3(1,0,0) ) );
Index: /trunk/src/engine/ragdoll_manager.cc
===================================================================
--- /trunk/src/engine/ragdoll_manager.cc	(revision 520)
+++ /trunk/src/engine/ragdoll_manager.cc	(revision 520)
@@ -0,0 +1,112 @@
+// 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/ragdoll_manager.hh"
+
+#include "nv/lua/lua_math.hh"
+
+using namespace nv;
+
+nv::ragdoll_manager::ragdoll_manager( model_manager* model )
+	: m_world( nullptr ), m_model( model )
+{
+
+}
+
+void nv::ragdoll_manager::initialize( lua::state* state )
+{
+	lua_resource_manager< ragdoll_data >::initialize( state );
+}
+
+bool nv::ragdoll_manager::load_resource( lua::table_guard& table, shash64 id )
+{
+	NV_ASSERT_ALWAYS( m_world, "Physics world not setup in ragdoll_manager!" );
+	bool result = false;
+	nv::string64 model_id = table.get_string64( "model" );
+	nv::resource< model > mr = m_model->get( model_id );
+	if ( !mr ) return false;
+	nv::resource< animator_bind_data > bindr;
+	if ( auto m = mr.lock() )
+		bindr = mr.lock()->bind_data;
+	if ( !bindr ) return false;
+
+	ragdoll_data* d = new ragdoll_data;
+	{
+		nv::lua::table_guard root( table, 1 );
+		result = load_ragdoll( root, bindr, d, -1 );
+	}
+	if ( result )
+	{
+		if ( auto bind = bindr.lock() )
+		{
+			const nv::data_node_list& bl = bind->get_bone_list();
+			d->bone_mask.resize( bl.size(), true );
+			for ( nv::uint32 i = 1; i < d->parts.size(); ++i )
+			{
+				d->bone_mask[d->parts[i].bone_id] = false;
+			}
+		}
+
+		add( id, d );
+	}
+	return result;
+}
+
+void nv::ragdoll_manager::release( ragdoll_data* p )
+{
+	for ( auto& part : p->parts )
+		m_world->release( part.shape );
+}
+
+bool nv::ragdoll_manager::load_ragdoll( lua::table_guard& table, resource< animator_bind_data > rbind, ragdoll_data* data, int pindex )
+{
+	if ( auto bind_data = rbind.lock() )
+	{
+		int index = data->parts.size();
+		data->parts.emplace_back();
+		auto& part = data->parts.back();
+		part.name = table.get_string_hash_64( "name" );
+		part.bone_id = nv::uint32( bind_data->get_bone_list().resolve( table.get_string_hash_64( "bone" ) ) );
+		NV_ASSERT( part.bone_id < 256, "Bone ID not found!" );
+		float radius = table.get_float( "radius" );
+		float length = 0.0f;
+		if ( table.has_field( "target" ) )
+		{
+			const auto& of = bind_data->get_bone_transforms().m_offsets;
+			int target = bind_data->get_bone_list().resolve( table.get_string_hash_64( "target" ) );
+			if ( target < 0 ) return false;
+			length = math::distance( of[part.bone_id].get_position(), of[target].get_position() );
+		}
+		else
+		{
+			length = table.get_float( "length" );
+		}
+		NV_ASSERT( radius > 0.0f && length > 0.0f, "Bad parameters!" );
+		part.shape      = m_world->create_capsule( radius, length );
+		part.parent_idx = pindex;
+		part.mass       = table.get_float( "mass", 1.0f );
+		part.cone_twist = false;
+		// Joints
+		if ( pindex != -1 )
+		{
+			part.cone_twist = table.get_string_hash_64("joint") == shash64( "cone_twist"_ls );
+			part.limits     = table.get< vec3 >( "limits" );
+		}
+		uint32 child_count = table.get_size();
+		bool   result = true;
+		if ( child_count > 0 )
+		{
+			for( uint32 i = 1; i <= child_count; ++i )
+			{
+				lua::table_guard child_table( table, i );
+				result = load_ragdoll( child_table, rbind, data, index );
+				if ( !result ) return false;
+			}
+		}
+		return result;
+	}
+	return false;
+}
Index: /trunk/src/gfx/debug_draw.cc
===================================================================
--- /trunk/src/gfx/debug_draw.cc	(revision 519)
+++ /trunk/src/gfx/debug_draw.cc	(revision 520)
@@ -101,4 +101,47 @@
 }
 
+void nv::debug_data::push_wire_capsule( const transform& tr, float radius, float height )
+{
+	vec3 p = tr.get_position();
+	quat o = tr.get_orientation();
+	vec3 dir   = o * vec3( 0, 1, 0 );
+	vec3 ldir  = o * vec3( 1, 0, 0 );
+	vec3 h1dir = o * normalize( vec3( 1, -1, 0 ) );
+	vec3 h2dir = o * normalize( vec3( 1, 1, 0 ) );
+	vec3 sp1[12];
+	vec3 sp2[12];
+	vec3 hsp1[12];
+	vec3 hsp2[12];
+	for ( uint32 i = 0; i < 12; ++i )
+	{
+		sp1[i]  = nv::math::rotate( ldir * radius, ( float( i ) / 12.0f ) * math::two_pi<float>(), dir );
+		hsp1[i] = nv::math::rotate( h1dir * radius, ( float( i ) / 12.0f ) * math::two_pi<float>(), dir );
+	}
+	for ( uint32 i = 0; i < 12; ++i )
+	{
+		sp2[i]  = nv::math::rotate( ldir * radius + height * dir, ( float( i ) / 12.0f ) * math::two_pi<float>(), dir );
+		hsp2[i] = nv::math::rotate( h2dir * radius + height * dir, ( float( i ) / 12.0f ) * math::two_pi<float>(), dir );
+	}
+
+	for ( uint32 i = 0; i < 12; ++i )
+	{
+		push_line( p + sp1[i], p + sp1[( i + 1 ) % 12], vec3( 1.0f, 0.0f, 0.0f ) );
+		push_line( p + sp2[i], p + sp2[( i + 1 ) % 12], vec3( 0.0f, 1.0f, 0.0f ) );
+		push_line( p + sp1[i], p + sp2[i], vec3( 0.0f, 0.0f, 1.0f ) );
+
+		push_line( p + hsp1[i], p + sp1[i], vec3( 0.0f, 0.0f, 1.0f ) );
+		push_line( p + hsp1[i], p + hsp1[( i + 1 ) % 12], vec3( 1.0f, 0.0f, 0.0f ) );
+		push_line( p + hsp2[i], p + sp2[i], vec3( 0.0f, 0.0f, 1.0f ) );
+		push_line( p + hsp2[i], p + hsp2[( i + 1 ) % 12], vec3( 0.0f, 1.0f, 0.0f ) );
+
+		push_line( p + hsp1[i], p - radius * dir, vec3( 0.0f, 0.0f, 1.0f ) );
+		push_line( p + hsp2[i], p + ( height + radius ) * dir, vec3( 0.0f, 0.0f, 1.0f ) );
+
+	}
+
+		
+}
+
+
 nv::debug_data::~debug_data()
 {
Index: /trunk/src/gfx/gfx_terminal.cc
===================================================================
--- /trunk/src/gfx/gfx_terminal.cc	(revision 519)
+++ /trunk/src/gfx/gfx_terminal.cc	(revision 520)
@@ -84,7 +84,5 @@
 		fgcolor = fg.get_argb32();
 		bgcolor = bg.get_argb32();
-		gylph   = uint32( ch );
-		NV_LOG_INFO( uint32( 
-			( fgcolor & uint32( 0x00FF0000 ) ) >> 16 ), "-", uint32( ( fgcolor & uint32( 0x0000FF00 ) ) >> 8 ),"-" , uint32( fgcolor & uint32( 0x000000FF ) ) );
+		gylph   = uint32( uint8(ch) );
 	}
 };
@@ -139,5 +137,5 @@
 	m_dc.p = m_context->create_program( nv_gfx_terminal_vs, nv_gfx_terminal_fs );
 
-	m_data->buffer = m_context->create_buffer( nv::UNIFORM_BUFFER, nv::DYNAMIC_DRAW, tsize.x * tsize.y * sizeof( gfx_terminal_uniform_block ), m_data->data );
+	m_data->buffer = m_context->create_buffer( nv::UNIFORM_BUFFER, nv::STREAM_DRAW, tsize.x * tsize.y * sizeof( gfx_terminal_uniform_block ), m_data->data );
 	m_context->bind( m_data->buffer, 7 );
 	m_context->get_device()->set_opt_uniform( m_dc.p, "term_size", vec2( tsize ) );
@@ -151,7 +149,10 @@
 void gfx_terminal::update()
 {
-	m_context->bind( m_data->buffer, 7 );
-	m_context->update( m_data->buffer, m_data->data, 0, m_data->size.x * m_data->size.y * sizeof( gfx_terminal_uniform_block ) );
-	m_update_needed = false;
+	if ( m_update_needed )
+	{
+		m_context->bind( m_data->buffer, 7 );
+		m_context->update( m_data->buffer, m_data->data, 0, m_data->size.x * m_data->size.y * sizeof( gfx_terminal_uniform_block ) );
+		m_update_needed = false;
+	}
 }
 
Index: /trunk/src/gfx/mesh_creator.cc
===================================================================
--- /trunk/src/gfx/mesh_creator.cc	(revision 519)
+++ /trunk/src/gfx/mesh_creator.cc	(revision 520)
@@ -87,4 +87,28 @@
 }
 
+
+nv::aabb nv::mesh_data_creator::calculate_aabb()
+{
+	NV_ASSERT_ALWAYS( m_pos_channel, "No position channel found!" );
+	vec3 minv;
+	vec3 maxv;
+
+	if ( m_pos_channel->size() > 0 )
+	{
+		minv = *reinterpret_cast<const vec3*>( m_pos_channel->raw_data() + m_pos_offset );
+		maxv = minv;
+	}
+
+	for ( uint32 c = 0; c < m_pos_channel->size(); ++c )
+	{
+		vec3 v = *reinterpret_cast<const vec3*>( m_pos_channel->raw_data() + c*m_pos_channel->element_size() + m_pos_offset );
+		minv = nv::math::min( minv, v );
+		maxv = nv::math::max( maxv, v );
+	}
+	vec3 extents  = maxv - minv;
+	vec3 hextents = extents * 0.5f;
+	vec3 halfv    = minv + hextents;
+	return aabb( nv::transform( halfv ), hextents );
+}
 
 void nv::mesh_data_creator::transform( const vec3& pos, const mat3& r33, float scale /*= 1.0f */ )
Index: /trunk/src/gfx/skeleton_instance.cc
===================================================================
--- /trunk/src/gfx/skeleton_instance.cc	(revision 519)
+++ /trunk/src/gfx/skeleton_instance.cc	(revision 520)
@@ -182,2 +182,19 @@
 }
 
+void nv::skeleton_transforms::delocalize_rec( const data_node_tree& node_data, uint32 id, const transform& parent, const array_view< bool >& mask )
+{
+	transform global_mat = parent;
+	bool b = mask[id];
+	if ( !b )
+		global_mat = m_transforms[id];
+	else
+	{
+		global_mat *= m_transforms[id];
+		m_transforms[id] = global_mat;
+	}
+	for ( auto child : node_data.children( id ) )
+	{
+		delocalize_rec( node_data, child, global_mat, mask );
+	}
+}
+
Index: /trunk/src/gl/gl_context.cc
===================================================================
--- /trunk/src/gl/gl_context.cc	(revision 519)
+++ /trunk/src/gl/gl_context.cc	(revision 520)
@@ -125,4 +125,16 @@
 	}
 }
+
+nv::image_data* nv::gl_context::dump_image( image_format f, image_data* reuse )
+{
+	NV_ASSERT_ALWAYS( f.type   == nv::UBYTE, "Bad format passed to dump" );
+	NV_ASSERT_ALWAYS( f.format == nv::RGB || f.format == nv::RGBA, "Bad format passed to dump" );
+	glPixelStorei( GL_PACK_ALIGNMENT, 1 );
+	image_data* result = reuse;
+	if ( !result ) result = new image_data( f, ivec2( m_viewport.z, m_viewport.w ) );
+	glReadPixels( 0, 0, m_viewport.z, m_viewport.w, f.format == nv::RGB ? GL_RGB : GL_RGBA, datatype_to_gl_enum( f.type ), const_cast< uint8* >( result->get_data() ) );
+	return result;
+}
+
 
 const framebuffer_info* nv::gl_context::get_framebuffer_info( framebuffer f ) const
Index: /trunk/src/gui/gui_ascii_renderer.cc
===================================================================
--- /trunk/src/gui/gui_ascii_renderer.cc	(revision 519)
+++ /trunk/src/gui/gui_ascii_renderer.cc	(revision 520)
@@ -98,5 +98,5 @@
 		m_terminal->print( abs.ll(), er->border_color, term_color(), er->border_chars[6] );
 		m_terminal->print( abs.lr,   er->border_color, term_color(), er->border_chars[7] );
-		m_terminal->update();
+		//m_terminal->update();
 	}
 	if ( !e->m_text.empty() )
Index: /trunk/src/gui/gui_environment.cc
===================================================================
--- /trunk/src/gui/gui_environment.cc	(revision 519)
+++ /trunk/src/gui/gui_environment.cc	(revision 520)
@@ -183,4 +183,5 @@
 			handle h = get_element( position( ev.mbutton.x, ev.mbutton.y ) );
 			element* e = m_elements.get( h );
+			if (e)
 			if ( e->m_on_click ) e->m_on_click();
 
Index: /trunk/src/image/miniz.cc
===================================================================
--- /trunk/src/image/miniz.cc	(revision 519)
+++ /trunk/src/image/miniz.cc	(revision 520)
@@ -2668,2 +2668,13 @@
 	return tinfl_decompress_mem_to_heap( source_buf, source_buf_len, out_len, parse_header ? TINFL_FLAG_PARSE_ZLIB_HEADER : 0 );
 }
+
+int nv::miniz_compress( unsigned char *pDest, unsigned long *pDest_len, const unsigned char *pSource, unsigned long source_len, int level )
+{
+	NV_PROFILE( "compress" );
+	return mz_compress2( pDest, pDest_len, pSource, source_len, level );
+}
+
+unsigned long nv::miniz_bound( unsigned long source_len )
+{
+	return mz_compressBound( source_len );
+}
Index: /trunk/src/image/png_writer.cc
===================================================================
--- /trunk/src/image/png_writer.cc	(revision 520)
+++ /trunk/src/image/png_writer.cc	(revision 520)
@@ -0,0 +1,378 @@
+// 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.
+
+#include "nv/image/png_writer.hh"
+
+#include "nv/image/miniz.hh"
+
+// TODO: remove
+#include <stdarg.h>  
+
+using namespace nv;
+
+#if NV_COMPILER == NV_CLANG
+#pragma clang diagnostic ignored "-Wunused-macros"
+#pragma clang diagnostic ignored "-Wold-style-cast"
+#pragma clang diagnostic ignored "-Wsign-conversion"
+#pragma clang diagnostic ignored "-Wreserved-id-macro"
+#pragma clang diagnostic ignored "-Wmissing-prototypes"
+#define NULL 0
+#endif
+
+enum
+{
+	STBI_default = 0, // only used for req_comp
+
+	STBI_grey = 1,
+	STBI_grey_alpha = 2,
+	STBI_rgb = 3,
+	STBI_rgb_alpha = 4
+};
+
+#define STBI_MALLOC(sz)     nvmalloc(sz)
+#define STBI_REALLOC(p,sz)  nvrealloc(p,sz)
+#define STBI_FREE(p)        nvfree(p)
+#define STBI_MEMMOVE(d,s,c) nvmemmove(d,s,c)
+
+typedef void stbi_write_func( void *context, void *data, int size );
+
+typedef struct
+{
+	stbi_write_func *func;
+	void *context;
+} stbi__write_context;
+
+// initialize a callback-based context
+static void stbi__start_write_callbacks( stbi__write_context *s, stbi_write_func *c, void *context )
+{
+	s->func = c;
+	s->context = context;
+}
+
+typedef unsigned int stbiw_uint32;
+typedef int stb_image_write_test[sizeof( stbiw_uint32 ) == 4 ? 1 : -1];
+
+template < typename T >
+inline uchar8 byte_cast( T x )
+{
+	return uchar8( ( x ) & 255 );
+}
+
+#define stbi__err(x,y)  0
+#define stbi__errpuc(x,y)  ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL))
+
+static void stbiw__writefv( stbi__write_context *s, const char *fmt, va_list v )
+{
+	while ( *fmt )
+	{
+		switch ( *fmt++ )
+		{
+		case ' ': break;
+		case '1': { unsigned char x = byte_cast( va_arg( v, int ) );
+			s->func( s->context, &x, 1 );
+			break; }
+		case '2': { int x = va_arg( v, int );
+			unsigned char b[2];
+			b[0] = byte_cast( x );
+			b[1] = byte_cast( x >> 8 );
+			s->func( s->context, b, 2 );
+			break; }
+		case '4': { stbiw_uint32 x = va_arg( v, int );
+			unsigned char b[4];
+			b[0] = byte_cast( x );
+			b[1] = byte_cast( x >> 8 );
+			b[2] = byte_cast( x >> 16 );
+			b[3] = byte_cast( x >> 24 );
+			s->func( s->context, b, 4 );
+			break; }
+		default:
+			byte_cast( 0 );
+			return;
+		}
+	}
+}
+
+static void stbiw__writef( stbi__write_context *s, const char *fmt, ... )
+{
+	va_list v;
+	va_start( v, fmt );
+	stbiw__writefv( s, fmt, v );
+	va_end( v );
+}
+
+static void stbiw__write3( stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c )
+{
+	unsigned char arr[3];
+	arr[0] = a, arr[1] = b, arr[2] = c;
+	s->func( s->context, arr, 3 );
+}
+
+static void stbiw__write_pixel( stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d )
+{
+	unsigned char bg[3] = { 255, 0, 255 }, px[3];
+	int k;
+
+	if ( write_alpha < 0 )
+		s->func( s->context, &d[comp - 1], 1 );
+
+	switch ( comp )
+	{
+	case 1:
+		s->func( s->context, d, 1 );
+		break;
+	case 2:
+		if ( expand_mono )
+			stbiw__write3( s, d[0], d[0], d[0] ); // monochrome bmp
+		else
+			s->func( s->context, d, 1 );  // monochrome TGA
+		break;
+	case 4:
+		if ( !write_alpha )
+		{
+			// composite against pink background
+			for ( k = 0; k < 3; ++k )
+				px[k] = bg[k] + ( ( d[k] - bg[k] ) * d[3] ) / 255;
+			stbiw__write3( s, px[1 - rgb_dir], px[1], px[1 + rgb_dir] );
+			break;
+		}
+		/* FALLTHROUGH */
+	case 3:
+		stbiw__write3( s, d[1 - rgb_dir], d[1], d[1 + rgb_dir] );
+		break;
+	}
+	if ( write_alpha > 0 )
+		s->func( s->context, &d[comp - 1], 1 );
+}
+
+static void stbiw__write_pixels( stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono )
+{
+	stbiw_uint32 zero = 0;
+	int i, j, j_end;
+
+	if ( y <= 0 )
+		return;
+
+	if ( vdir < 0 )
+		j_end = -1, j = y - 1;
+	else
+		j_end = y, j = 0;
+
+	for ( ; j != j_end; j += vdir )
+	{
+		for ( i = 0; i < x; ++i )
+		{
+			unsigned char *d = (unsigned char *)data + ( j*x + i )*comp;
+			stbiw__write_pixel( s, rgb_dir, comp, write_alpha, expand_mono, d );
+		}
+		s->func( s->context, &zero, scanline_pad );
+	}
+}
+
+static int stbiw__outfile( stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ... )
+{
+	if ( y < 0 || x < 0 )
+	{
+		return 0;
+	}
+	else
+	{
+		va_list v;
+		va_start( v, fmt );
+		stbiw__writefv( s, fmt, v );
+		va_end( v );
+		stbiw__write_pixels( s, rgb_dir, vdir, x, y, comp, data, alpha, pad, expand_mono );
+		return 1;
+	}
+}
+
+
+static unsigned int stbiw__crc32( unsigned char *buffer, int len )
+{
+	static unsigned int crc_table[256] =
+	{
+		0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
+		0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
+		0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
+		0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
+		0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
+		0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
+		0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
+		0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
+		0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
+		0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
+		0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
+		0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
+		0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
+		0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
+		0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
+		0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
+		0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
+		0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
+		0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
+		0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
+		0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
+		0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
+		0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
+		0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
+		0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
+		0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
+		0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
+		0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
+		0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
+		0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
+		0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
+		0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
+	};
+
+	unsigned int crc = ~0u;
+	int i;
+	for ( i = 0; i < len; ++i )
+		crc = ( crc >> 8 ) ^ crc_table[buffer[i] ^ ( crc & 0xff )];
+	return ~crc;
+}
+
+#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=byte_cast(a),(o)[1]=byte_cast(b),(o)[2]=byte_cast(c),(o)[3]=byte_cast(d),(o)+=4)
+#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v));
+#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3])
+
+static void stbiw__wpcrc( unsigned char **data, int len )
+{
+	unsigned int crc = stbiw__crc32( *data - len - 4, len + 4 );
+	stbiw__wp32( *data, crc );
+}
+
+static unsigned char stbiw__paeth( int a, int b, int c )
+{
+	int p = a + b - c, pa = abs( p - a ), pb = abs( p - b ), pc = abs( p - c );
+	if ( pa <= pb && pa <= pc ) return byte_cast( a );
+	if ( pb <= pc ) return byte_cast( b );
+	return byte_cast( c );
+}
+
+unsigned char * stbi_zlib_compress( unsigned char *data, int data_len, int *out_len, int quality )
+{
+	unsigned long expected = nv::miniz_bound( data_len );
+	unsigned char* result = (unsigned char *)STBI_MALLOC( expected );
+	unsigned long rlen = expected;
+	nv::miniz_compress( result, &rlen, data, data_len, 6 );
+	*out_len = rlen;
+	return result;
+}
+
+unsigned char *stbi_write_png_to_mem( const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len )
+{
+	int ctype[5] = { -1, 0, 4, 2, 6 };
+	unsigned char sig[8] = { 137,80,78,71,13,10,26,10 };
+	unsigned char *out, *o, *filt, *zlib;
+	signed char *line_buffer;
+	int i, j, k, p, zlen;
+
+	if ( stride_bytes == 0 )
+		stride_bytes = x * n;
+
+	filt = (unsigned char *)STBI_MALLOC( ( x*n + 1 ) * y ); if ( !filt ) return 0;
+	line_buffer = (signed char *)STBI_MALLOC( x * n ); if ( !line_buffer ) { STBI_FREE( filt ); return 0; }
+
+	bool flip_y = true;
+
+	for ( j = 0; j < y; ++j )
+	{
+		int line = flip_y ? y - j - 1 : j;
+		int step = flip_y ? -stride_bytes : stride_bytes;
+		static int mapping[] = { 0,1,2,3,4 };
+		static int firstmap[] = { 0,1,0,5,6 };
+		int *mymap = j ? mapping : firstmap;
+		int best = 0, bestval = 0x7fffffff;
+		for ( p = 0; p < 2; ++p )
+		{
+			for ( k = p ? best : 0; k < 5; ++k )
+			{
+				int type = mymap[k], est = 0;
+				const unsigned char *z = pixels + stride_bytes*line;
+				for ( i = 0; i < n; ++i )
+					switch ( type )
+					{
+					case 0: line_buffer[i] = z[i]; break;
+					case 1: line_buffer[i] = z[i]; break;
+					case 2: line_buffer[i] = z[i] - z[i - step]; break;
+					case 3: line_buffer[i] = z[i] - ( z[i - step] >> 1 ); break;
+					case 4: line_buffer[i] = (signed char)( z[i] - stbiw__paeth( 0, z[i - step], 0 ) ); break;
+					case 5: line_buffer[i] = z[i]; break;
+					case 6: line_buffer[i] = z[i]; break;
+					}
+				for ( i = n; i < x*n; ++i )
+				{
+					switch ( type )
+					{
+					case 0: line_buffer[i] = z[i]; break;
+					case 1: line_buffer[i] = z[i] - z[i - n]; break;
+					case 2: line_buffer[i] = z[i] - z[i - step]; break;
+					case 3: line_buffer[i] = z[i] - ( ( z[i - n] + z[i - step] ) >> 1 ); break;
+					case 4: line_buffer[i] = z[i] - stbiw__paeth( z[i - n], z[i - step], z[i - step - n] ); break;
+					case 5: line_buffer[i] = z[i] - ( z[i - n] >> 1 ); break;
+					case 6: line_buffer[i] = z[i] - stbiw__paeth( z[i - n], 0, 0 ); break;
+					}
+				}
+				if ( p ) break;
+				for ( i = 0; i < x*n; ++i )
+					est += abs( (signed char)line_buffer[i] );
+				if ( est < bestval ) { bestval = est; best = k; }
+			}
+		}
+		// when we get here, best contains the filter type, and line_buffer contains the data
+		//line = j;
+		filt[j*( x*n + 1 )] = (unsigned char)best;
+		STBI_MEMMOVE( filt + j*( x*n + 1 ) + 1, line_buffer, x*n );
+	}
+	STBI_FREE( line_buffer );
+	zlib = stbi_zlib_compress( filt, y*( x*n + 1 ), &zlen, 8 ); // increase 8 to get smaller but use more memory
+	STBI_FREE( filt );
+	if ( !zlib ) return 0;
+
+	// each tag requires 12 bytes of overhead
+	out = (unsigned char *)STBI_MALLOC( 8 + 12 + 13 + 12 + zlen + 12 );
+	if ( !out ) return 0;
+	*out_len = 8 + 12 + 13 + 12 + zlen + 12;
+
+	o = out;
+	STBI_MEMMOVE( o, sig, 8 ); o += 8;
+	stbiw__wp32( o, 13 ); // header length
+	stbiw__wptag( o, "IHDR" );
+	stbiw__wp32( o, x );
+	stbiw__wp32( o, y );
+	*o++ = 8;
+	*o++ = byte_cast( ctype[n] );
+	*o++ = 0;
+	*o++ = 0;
+	*o++ = 0;
+	stbiw__wpcrc( &o, 13 );
+
+	stbiw__wp32( o, zlen );
+	stbiw__wptag( o, "IDAT" );
+	STBI_MEMMOVE( o, zlib, zlen );
+	o += zlen;
+	STBI_FREE( zlib );
+	stbiw__wpcrc( &o, zlen );
+
+	stbiw__wp32( o, 0 );
+	stbiw__wptag( o, "IEND" );
+	stbiw__wpcrc( &o, 0 );
+
+	NV_ASSERT( o == out + *out_len, "wrong output" );
+
+	return out;
+}
+
+bool nv::png_writer::save( stream& s, image_data* data )
+{
+	ivec2 sz = data->get_size();
+	int len = 0;
+	unsigned char* out = stbi_write_png_to_mem( data->get_data(), 0, sz.x, sz.y, data->get_depth(), &len );
+	if ( !out ) return false;
+	s.write( out, 1, len );
+	STBI_FREE( out );
+	return true;
+}
Index: /trunk/src/lib/gl.cc
===================================================================
--- /trunk/src/lib/gl.cc	(revision 519)
+++ /trunk/src/lib/gl.cc	(revision 520)
@@ -189,5 +189,5 @@
 
 
-	HWND hWndFake = CreateWindow(TEXT("Dummy67789"), "FAKE", WS_OVERLAPPEDWINDOW | WS_MAXIMIZE | WS_CLIPCHILDREN, 
+	HWND hWndFake = CreateWindowA("Dummy67789", "FAKE", WS_OVERLAPPEDWINDOW | WS_MAXIMIZE | WS_CLIPCHILDREN,
 		0, 0, CW_USEDEFAULT, CW_USEDEFAULT, NULL, 
 		NULL, GetModuleHandle(nullptr), NULL); 
@@ -213,5 +213,5 @@
 	wgl_makecurrent(hDC, hRCFake); 
 
-	LoadLibrary( "gdi32.dll" );
+	LoadLibraryA( "gdi32.dll" );
 	bool gl_loaded = nv::load_gl_library( path );
 	bool wgl_loaded = nv::load_wgl_library( path );
Index: /trunk/src/lua/lua_area.cc
===================================================================
--- /trunk/src/lua/lua_area.cc	(revision 519)
+++ /trunk/src/lua/lua_area.cc	(revision 520)
@@ -8,5 +8,5 @@
 
 #include "nv/lua/lua_raw.hh"
-#include "nv/core/random.hh"
+#include "nv/lua/lua_aux.hh"
 
 using nv::lua::detail::is_coord;
@@ -333,5 +333,5 @@
 	nv::sint32 xs = ( b.x - a.x ) + 1;
 	nv::sint32 ys = ( b.y - a.y ) - 1;
-	nv::sint32 roll = nv::random::get().srand( 2 * xs + 2 * ys );
+	nv::sint32 roll = nv::lua::rng().srand( 2 * xs + 2 * ys );
 	nv::ivec2 result;
 
@@ -356,5 +356,5 @@
 	nv::sint32 xs = ( b.x - a.x ) - 1;
 	nv::sint32 ys = ( b.y - a.y ) - 1;
-	nv::sint32 roll = nv::random::get().srand( 2 * xs + 2 * ys );
+	nv::sint32 roll = nv::lua::rng().srand( 2 * xs + 2 * ys );
 	nv::ivec2 result;
 
@@ -375,5 +375,5 @@
 {
 	nv::rectangle area = to_area( L, 1 );
-	push_coord( L, nv::random::get().range( area.ul, area.lr ) );
+	push_coord( L, nv::lua::rng().range( area.ul, area.lr ) );
 	return 1;
 }
@@ -383,5 +383,5 @@
 	nv::rectangle area  = to_area( L, 1 );
 	nv::ivec2     dim   = to_coord( L, 2 );
-	nv::ivec2     start = nv::random::get().range( area.ul, area.lr - dim );
+	nv::ivec2     start = nv::lua::rng().range( area.ul, area.lr - dim );
 	push_area( L, nv::rectangle( start, start + dim ) );
 	return 1;
Index: /trunk/src/lua/lua_aux.cc
===================================================================
--- /trunk/src/lua/lua_aux.cc	(revision 519)
+++ /trunk/src/lua/lua_aux.cc	(revision 520)
@@ -9,4 +9,6 @@
 #include "nv/lua/lua_raw.hh"
 #include "nv/core/random.hh"
+
+static nv::random lua_rng;
 
 static int nluaaux_table_copy( lua_State* L )
@@ -94,5 +96,5 @@
 	if ( dice < 1 )  luaL_argerror( L, 1, "die count lower than 1!" );
 	if ( sides < 1 ) luaL_argerror( L, 2, "side count lower than 1!" );
-	lua_pushnumber( L, nv::random::get().dice( static_cast< nv::uint32 >( dice ), static_cast< nv::uint32 >( sides ) ) );
+	lua_pushnumber( L, lua_rng.dice( static_cast< nv::uint32 >( dice ), static_cast< nv::uint32 >( sides ) ) );
 	return 1;
 }
@@ -102,5 +104,5 @@
 	if ( lua_gettop( L ) == 0 )
 	{
-		lua_pushnumber( L, nv::random::get().frand(1.0f) );
+		lua_pushnumber( L, lua_rng.frand(1.0f) );
 	}
 	else
@@ -110,5 +112,5 @@
 			lua_Integer arg1 = luaL_checkinteger( L, 1 );
 			if ( arg1 < 1 ) arg1 = 1;
-			nlua_pushunsigned( L, nv::random::get().urange( 1, static_cast<nv::uint32>( arg1 ) ) );
+			nlua_pushunsigned( L, lua_rng.urange( 1, static_cast<nv::uint32>( arg1 ) ) );
 		}
 		else
@@ -116,5 +118,5 @@
 			int arg1 = static_cast< int >( luaL_checkinteger( L, 1 ) );
 			int arg2 = static_cast< int >( luaL_checkinteger( L, 2 ) );
-			int result = ( arg2 >= arg1 ? nv::random::get().srange( arg1, arg2 ) : nv::random::get().srange( arg2, arg1 ) );
+			int result = ( arg2 >= arg1 ? lua_rng.srange( arg1, arg2 ) : lua_rng.srange( arg2, arg1 ) );
 			lua_pushinteger( L, result );
 		}
@@ -125,5 +127,5 @@
 static int nluaaux_math_randomseed( lua_State* L )
 {
-	nv::random::get().set_seed( nlua_tounsigned( L, 1 ) );
+	lua_rng.set_seed( nlua_tounsigned( L, 1 ) );
 	return 0;
 }
@@ -137,4 +139,9 @@
 };
 
+nv::random & nv::lua::rng()
+{
+	return lua_rng;
+}
+
 void nv::lua::register_aux( lua::state* state )
 {
Index: /trunk/src/lua/lua_map_tile.cc
===================================================================
--- /trunk/src/lua/lua_map_tile.cc	(revision 519)
+++ /trunk/src/lua/lua_map_tile.cc	(revision 520)
@@ -11,5 +11,5 @@
 #include "nv/stl/numeric.hh"
 #include "nv/stl/algorithm.hh"
-#include "nv/core/random.hh"
+#include "nv/lua/lua_aux.hh"
 #include "nv/lua/lua_area.hh"
 #include "nv/lua/lua_math.hh"
@@ -207,5 +207,5 @@
 static int nlua_map_tile_flip_random( lua_State* L )
 {
-	switch ( nv::random::get().urand( 4 ) )
+	switch ( nv::lua::rng().urand( 4 ) )
 	{
 	case 1 : nlua_map_tile_flip_x( L ); break;
Index: /trunk/src/lua/lua_math.cc
===================================================================
--- /trunk/src/lua/lua_math.cc	(revision 519)
+++ /trunk/src/lua/lua_math.cc	(revision 520)
@@ -8,5 +8,5 @@
 
 #include "nv/lua/lua_raw.hh"
-#include "nv/core/random.hh"
+#include "nv/lua/lua_aux.hh"
 #include "nv/stl/type_traits/common.hh"
 
@@ -126,5 +126,5 @@
 int nlua_vec_random( lua_State* L )
 {
-	push_vec<T>( L, nv::random::get().range( to_vec<T>( L, 1 ), to_vec<T>( L, 2 ) ) );
+	push_vec<T>( L, nv::lua::rng().range( to_vec<T>( L, 1 ), to_vec<T>( L, 2 ) ) );
 	return 1;
 }
