Index: trunk/src/gfx/particle_engine.cc
===================================================================
--- trunk/src/gfx/particle_engine.cc	(revision 306)
+++ trunk/src/gfx/particle_engine.cc	(revision 306)
@@ -0,0 +1,417 @@
+#include "nv/gfx/particle_engine.hh"
+
+#include <nv/interface/device.hh>
+#include <nv/random.hh>
+#include <nv/lua/lua_glm.hh>
+#include <nv/logging.hh>
+
+static const char *nv_particle_engine_vertex_shader_world =
+	"#version 120\n"
+	"attribute vec3 nv_position;\n"
+	"attribute vec2 nv_texcoord;\n"
+	"attribute vec4 nv_color;\n"
+	"varying vec4 v_color;\n"
+	"varying vec2 v_texcoord;\n"
+	"uniform mat4 nv_m_view;\n"
+	"uniform mat4 nv_m_projection;\n"
+	"void main(void)\n"
+	"{\n"
+	"	gl_Position = nv_m_projection * nv_m_view * vec4 (nv_position, 1.0);\n"
+	"	v_texcoord  = nv_texcoord;\n"
+	"	v_color     = nv_color;\n"
+	"}\n";
+static const char *nv_particle_engine_vertex_shader_local =
+	"#version 120\n"
+	"attribute vec3 nv_position;\n"
+	"attribute vec2 nv_texcoord;\n"
+	"attribute vec4 nv_color;\n"
+	"varying vec4 v_color;\n"
+	"varying vec2 v_texcoord;\n"
+	"uniform mat4 nv_m_mvp;\n"
+	"void main(void)\n"
+	"{\n"
+	"	gl_Position = nv_m_mvp * vec4 (nv_position, 1.0);\n"
+	"	v_texcoord  = nv_texcoord;\n"
+	"	v_color     = nv_color;\n"
+	"}\n";
+static const char *nv_particle_engine_fragment_shader =
+	"#version 120\n"
+	"uniform sampler2D nv_t_diffuse;\n"
+	"varying vec4 v_color;\n"
+	"varying vec2 v_texcoord;\n"
+	"void main(void)\n"
+	"{\n"
+	"	vec4 tex_color = texture2D( nv_t_diffuse, v_texcoord );\n"
+	"	gl_FragColor   = v_color * tex_color;\n"
+	"}\n";
+
+void nv::particle_engine::load( lua::table_guard& table )
+{
+	std::string id = table.get_string( "id" );
+	if ( id == "" )
+	{
+		NV_LOG( LOG_ERROR, "Bad table passed to particle_engine!" )
+	}
+	// TODO : overwrite check
+	m_names[ id ] = m_data.size();
+
+	m_data.emplace_back();
+	auto& data = m_data.back();
+
+	data.gravity = table.get<vec3>("gravity", vec3() );
+	data.quota   = table.get<uint32>("quota", 1024 );
+	data.local   = table.get<bool>("local", false );
+	data.accurate_facing = table.get<bool>("accurate_facing", false );
+	data.emmiter_count   = 0;
+
+	std::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( LOG_ERROR, "Unknown orientation type! (" << orientation << " is MAX)!" );
+		data.orientation = particle_orientation::POINT;
+	}
+	data.common_up  = glm::normalize( table.get<vec3>("common_up",  vec3(1,0,0) ) );
+	data.common_dir = glm::normalize( table.get<vec3>("common_dir", vec3(0,1,0) ) );
+
+	uint32 elements = table.get_size();
+	for ( uint32 i = 0; i < elements; ++i )
+	{
+		lua::table_guard element( table, i+1 );
+		std::string type  = element.get_string("type");
+		std::string etype = element.get_string("etype");
+		if ( type == "emmiter" )
+		{
+			if ( data.emmiter_count < MAX_PARTICLE_EMMITERS )
+			{
+				particle_emmiter_data& edata = data.emmiters[ data.emmiter_count ];
+				vec4 color         = element.get<vec4>("color", vec4(1,1,1,1) );
+				edata.color_min    = element.get<vec4>("color_min", color );
+				edata.color_max    = element.get<vec4>("color_max", color );
+				vec2 size          = element.get<vec2>("size", vec2(0.1,0.1) );
+				edata.size_min     = element.get<vec2>("size_min", size );
+				edata.size_max     = element.get<vec2>("size_max", size );
+				edata.square       = element.get<bool>("square", true );
+				edata.angle        = element.get<float>("angle", 0.0f );
+				float velocity     = element.get<float>("velocity", 0.0f );
+				edata.velocity_min = element.get<float>("velocity_min", velocity );
+				edata.velocity_max = element.get<float>("velocity_max", velocity );
+				float lifetime     = element.get<float>("lifetime", 1.0f );
+				edata.lifetime_min = uint32( element.get<float>("lifetime_min", lifetime ) * 1000.f );
+				edata.lifetime_max = uint32( element.get<float>("lifetime_max", lifetime ) * 1000.f );
+				edata.rate         = element.get<float>("rate", 1.0f );
+				edata.dir          = glm::normalize( element.get<vec3>("direction", vec3(0,1,0) ) );
+				
+				edata.odir = glm::vec3( 0, 0, 1 );
+				if ( edata.dir != vec3( 0, 1, 0 ) && edata.dir != vec3( 0, -1, 0 ) )
+					edata.odir = glm::normalize( glm::cross( edata.dir, vec3( 0, 1, 0 ) ) );		edata.cdir = glm::cross( edata.dir, edata.odir );
+
+				data.emmiter_count++;
+			}
+			else
+			{
+				NV_LOG( LOG_ERROR, "Too many emmiters (" << MAX_PARTICLE_EMMITERS << " is MAX)!" );
+			}
+		}
+		else if ( type == "affector" )
+		{
+
+		}
+		else 
+		{
+			NV_LOG( LOG_WARNING, "Unknown element in particle system! (" << type << ")" );
+		}
+	}
+
+}
+
+nv::particle_engine::particle_engine( context* a_context )
+{
+	m_context       = a_context;
+	m_device        = a_context->get_device();
+	m_program_local = m_device->create_program( nv_particle_engine_vertex_shader_local, nv_particle_engine_fragment_shader );
+	m_program_world = m_device->create_program( nv_particle_engine_vertex_shader_world, nv_particle_engine_fragment_shader );
+}
+
+nv::particle_system nv::particle_engine::create_system( const std::string& id )
+{
+	auto it = m_names.find( id );
+	if ( it == m_names.end() )
+	{
+		return particle_system();
+	}
+	const particle_system_data* data = &(m_data[it->second]);
+	particle_system result = m_systems.create();
+	particle_system_info* info = m_systems.get( result );
+
+	info->data     = data;
+	uint32 ecount = data->emmiter_count;
+	for ( uint32 i = 0; i < ecount; ++i )
+	{
+		info->emmiters[i].last_create = 0;
+	}
+
+	info->count = 0;
+	info->particles = new particle[ data->quota ];
+	info->quads     = new particle_quad[ data->quota ];
+	info->vtx_array = m_device->create_vertex_array<particle_vtx>( 
+		(particle_vtx*)info->quads, data->quota*6, STREAM_DRAW );
+	info->vtx_buffer = m_device->find_buffer( info->vtx_array, slot::POSITION );
+	info->last_update = 0;
+	info->test = false;
+//	result->m_own_va      = true;
+//	result->m_offset      = 0;
+
+	return result;
+}
+
+void nv::particle_engine::draw( particle_system system, const render_state& rs, const scene_state& ss )
+{
+	particle_system_info* info = m_systems.get( system );
+	if ( info )
+	{
+		m_context->draw( nv::TRIANGLES, rs, ss, info->data->local ?  m_program_local : m_program_world, info->vtx_array, info->count * 6 );
+	}
+}
+
+nv::particle_engine::~particle_engine()
+{
+	m_device->release( m_program_world );
+	m_device->release( m_program_local );
+}
+
+void nv::particle_engine::release( particle_system system )
+{
+	particle_system_info* info = m_systems.get( system );
+	if ( info )
+	{
+		delete[] info->particles;
+		delete[] info->quads;
+		//if ( system->own_va ) 
+		m_device->release( info->vtx_array );
+		m_systems.destroy( system );
+	}
+}
+
+void nv::particle_engine::update( particle_system system, const scene_state& s, uint32 ms )
+{
+	particle_system_info* info = m_systems.get( system );
+	if ( info )
+	{
+		m_view_matrix  = s.get_view();
+		m_model_matrix = s.get_model();
+		m_camera_pos   = s.get_camera().get_position();
+		m_inv_view_dir = glm::normalize(-s.get_camera().get_direction());
+
+		destroy_particles( info, ms );
+		create_particles( info, ms );
+		update_particles( info, ms );
+
+		generate_data( info );
+		m_context->update( info->vtx_buffer, info->quads, /*system->m_offset*sizeof(particle_quad)*/ 0, info->count*sizeof(particle_quad) );
+	}
+}
+
+void nv::particle_engine::set_texcoords( particle_system system, vec2 a, vec2 b )
+{
+	particle_system_info* info = m_systems.get( system );
+	if ( info )
+	{
+		vec2 texcoords[4] = { a, vec2( b.x, a.y ), vec2( a.x, b.y ), b };
+
+		for ( uint32 i = 0; i < info->data->quota; ++i )
+		{
+			particle_quad& rdata   = info->quads[i];
+			rdata.data[0].texcoord = texcoords[0];
+			rdata.data[1].texcoord = texcoords[1];
+			rdata.data[2].texcoord = texcoords[2];
+			rdata.data[3].texcoord = texcoords[3];
+			rdata.data[4].texcoord = texcoords[2];
+			rdata.data[5].texcoord = texcoords[1];
+		}
+	}
+}
+
+void nv::particle_engine::generate_data( particle_system_info* info )
+{
+	const vec3 x( 0.5f, 0.0f, 0.0f );
+	const vec3 y( 0.0f, 0.5f, 0.0f );
+	const vec3 z( 0.0f, 0.0f ,1.0f );
+
+	const vec3 sm[4] = {
+		vec3( -x-y ),
+		vec3(  x-y ),
+		vec3( -x+y ),
+		vec3(  x+y )
+	};
+
+	mat3 rot_mat;
+	vec3 right;
+	mat3 irot_mat = glm::transpose( mat3( m_view_matrix ) );
+	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;
+
+
+	for ( uint32 i = 0; i < info->count; ++i )
+	{
+		const particle& pdata = info->particles[i];
+		particle_quad& rdata  = info->quads[i];
+
+		vec3 view_dir( m_inv_view_dir );
+		if ( accurate_facing ) view_dir = glm::normalize( m_camera_pos - pdata.position );
+
+		switch ( orientation )
+		{
+		case particle_orientation::POINT :
+			right   = glm::normalize( glm::cross( view_dir, vec3( 0, 1, 0 ) ) );
+			rot_mat = mat3( right, glm::cross( right, -view_dir ), -view_dir );
+//			rot_mat = irot_mat * glm::mat3_cast( glm::angleAxis( pdata.rotation, z ) );
+			break;
+		case particle_orientation::ORIENTED :
+			right   = glm::normalize( glm::cross( pdata.dir, view_dir ) );
+			rot_mat = mat3( right, pdata.dir, glm::cross( pdata.dir, right ) );
+			break;
+		case particle_orientation::ORIENTED_COMMON :
+			right   = glm::normalize( glm::cross( common_dir, view_dir ) );
+			rot_mat = mat3( right, common_dir, glm::cross( common_dir, right ) );
+			break;
+		case particle_orientation::PERPENDICULAR :
+			right   = glm::normalize( glm::cross( common_up, pdata.dir ) );
+			rot_mat = mat3( right, common_up, glm::cross( common_up, right ) );
+			break;
+		case particle_orientation::PERPENDICULAR_COMMON :
+			right   = glm::normalize( glm::cross( common_up, common_dir ) );
+			rot_mat = mat3( right, common_up, glm::cross( common_up, right ) );
+			break;
+		}
+
+		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[1].position = pdata.position + s1;
+		rdata.data[1].color    = pdata.color;
+
+		rdata.data[2].position = pdata.position + s2;
+		rdata.data[2].color    = pdata.color;
+
+		rdata.data[3].position = pdata.position + s3;
+		rdata.data[3].color    = pdata.color;
+
+		rdata.data[4] = rdata.data[2];
+		rdata.data[5] = rdata.data[1];
+	}
+}
+
+void nv::particle_engine::destroy_particles( particle_system_info* info, uint32 ms )
+{
+	if ( info->count > 0 )
+		for ( sint32 i = info->count-1; i >= 0; --i )
+		{
+			particle& pinfo = info->particles[i];
+			if ( //pdata.position.y < 0.0f ||
+				ms >= pinfo.death )
+			{
+				info->count--;
+				std::swap( info->particles[i], info->particles[info->count] );
+			}
+		}
+}
+
+void nv::particle_engine::create_particles( particle_system_info* info, uint32 ms )
+{
+	uint32 ecount = info->data->emmiter_count;
+	if ( ecount == 0 ) return;
+
+	random& r = random::get();
+	vec3 source;
+	mat3 orient;
+	if ( !info->data->local ) 
+	{
+		source = vec3( m_model_matrix[3] );
+		orient = mat3( m_model_matrix );
+	}
+
+	for ( uint32 i = 0; i < ecount; ++i )
+	{
+		const auto& edata = info->data->emmiters[i];
+		auto& einfo = info->emmiters[i];
+
+		uint32 period = glm::max<uint32>( uint32(1000.f / edata.rate), 1 );
+		while ( ms - einfo.last_create > period )
+		{
+			if ( info->count < info->data->quota-1 )
+			{
+				particle& pinfo = info->particles[info->count];
+				pinfo.position = source;
+				pinfo.color    = edata.color_min == edata.color_max ? 
+					edata.color_min : r.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 );
+				if ( edata.square ) pinfo.size.y = pinfo.size.x;
+				pinfo.velocity  = edata.velocity_min == edata.velocity_max ?
+					edata.velocity_min : r.frange( edata.velocity_min, edata.velocity_max );
+				pinfo.death     = ms + ( edata.lifetime_min == edata.lifetime_max ?
+					edata.lifetime_min : r.urange( edata.lifetime_min, edata.lifetime_max ) );
+				//pinfo.rotation = r.frand( 360.0f );
+
+				pinfo.dir = edata.dir;
+				if ( edata.angle > 0.0f )
+				{
+					float emission_angle = glm::radians( edata.angle );
+					float cos_theta = r.frange( cos( emission_angle ), 1.0f );
+					float sin_theta = glm::sqrt(1.0f - cos_theta * cos_theta );
+					float phi       = r.frange( 0.0f, 2*glm::pi<float>() );
+					pinfo.dir = orient * 
+						( edata.odir * ( glm::cos(phi) * sin_theta ) +
+						edata.cdir * ( glm::sin(phi)*sin_theta ) + 
+						edata.dir  * cos_theta );
+				}
+
+				info->count++;
+			}
+			einfo.last_create += period;
+		}
+	}
+}
+
+void nv::particle_engine::update_particles( particle_system_info* system, uint32 ms )
+{
+	uint32 ticks = ms - system->last_update;
+	if ( ticks > 0 )
+		for ( uint32 i = 0; i < system->count; ++i )
+		{
+			particle& pdata = system->particles[i];
+			vec3 velocity_vec = pdata.dir * pdata.velocity;
+			pdata.position += velocity_vec * ( 0.001f * ticks );
+			velocity_vec   += system->data->gravity * ( 0.001f * ticks );
+			pdata.velocity  = glm::length( velocity_vec );
+			if ( pdata.velocity > 0.0f ) pdata.dir       = glm::normalize( velocity_vec );
+			if ( pdata.position.y < 0.0f )
+			{
+				if ( pdata.velocity > 1.0f )
+				{
+					pdata.position.y = -pdata.position.y;
+					pdata.velocity   = 0.5f*pdata.velocity;
+					pdata.dir.y      = -pdata.dir.y;
+				}
+				else
+				{
+					pdata.position.y = 0.0f;
+					pdata.velocity   = 0.0f;
+				}
+			}
+		}
+		system->last_update = ms;
+}
