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;
+}
