#include "nv/gfx/particle_engine.hh" #include #include #include #include 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("gravity", vec3() ); data.quota = table.get("quota", 1024 ); data.local = table.get("local", false ); data.accurate_facing = table.get("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("common_up", vec3(1,0,0) ) ); data.common_dir = glm::normalize( table.get("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("color", vec4(1,1,1,1) ); edata.color_min = element.get("color_min", color ); edata.color_max = element.get("color_max", color ); vec2 size = element.get("size", vec2(0.1,0.1) ); edata.size_min = element.get("size_min", size ); edata.size_max = element.get("size_max", size ); edata.square = element.get("square", true ); edata.angle = element.get("angle", 0.0f ); float velocity = element.get("velocity", 0.0f ); edata.velocity_min = element.get("velocity_min", velocity ); edata.velocity_max = element.get("velocity_max", velocity ); float lifetime = element.get("lifetime", 1.0f ); edata.lifetime_min = uint32( element.get("lifetime_min", lifetime ) * 1000.f ); edata.lifetime_max = uint32( element.get("lifetime_max", lifetime ) * 1000.f ); edata.rate = element.get("rate", 1.0f ); edata.dir = glm::normalize( element.get("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*)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(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() ); 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; }