// Copyright (C) 2014-2015 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_engine.hh" #include #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" "varying vec3 v_position;\n" "uniform mat4 nv_m_view;\n" "uniform mat4 nv_m_projection;\n" "void main(void)\n" "{\n" " v_position = nv_position;\n" " v_texcoord = nv_texcoord;\n" " v_color = nv_color;\n" " gl_Position = nv_m_projection * nv_m_view * vec4 (nv_position, 1.0);\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" "varying vec3 v_position;\n" "uniform mat4 nv_m_mvp;\n" "void main(void)\n" "{\n" " v_position = nv_position;\n" " v_texcoord = nv_texcoord;\n" " v_color = nv_color;\n" " gl_Position = nv_m_mvp * vec4 (nv_position, 1.0);\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" "varying vec3 v_position;\n" "void main(void)\n" "{\n" " vec4 tex_color = texture2D( nv_t_diffuse, v_texcoord );\n" " float edge = smoothstep( 0.0, 0.1, v_position.y );\n" " gl_FragColor = v_color * tex_color * edge;\n" "}\n"; using namespace nv; static void nv_particle_emmiter_point( const particle_emmiter_data*, particle* p, uint32 count ) { for ( uint32 i = 0; i < count; ++i ) { p[i].position = vec3(); } } static void nv_particle_emmiter_box( const particle_emmiter_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_emmiter_cylinder( const particle_emmiter_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] ); p[i].position = rellipse.x * pe->cdir + r.frange( 0.0f, pe->extents[1] ) * pe->dir + rellipse.y * pe->odir; } } static void nv_particle_emmiter_sphere( const particle_emmiter_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]; p[i].position = rsphere.x * pe->cdir + rsphere.y * pe->dir + rsphere.z * pe->odir; } } static void nv_particle_emmiter_cylindroid( const particle_emmiter_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 ); p[i].position = rellipse.x * pe->cdir + r.frange( 0.0f, pe->extents[1] ) * pe->dir + rellipse.y * pe->odir; } } static void nv_particle_emmiter_ellipsoid( const particle_emmiter_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 ); p[i].position = rsphere.x * pe->cdir + rsphere.y * pe->dir + rsphere.z * pe->odir; } } static void nv_particle_emmiter_hollow_cylinder( const particle_emmiter_data* pe, particle* p, uint32 count ) { random& r = random::get(); for ( uint32 i = 0; i < count; ++i ) { vec2 rellipse = r.hollow_disk_point( pe->ihextents[0], pe->hextents[0], pe->precise ); p[i].position = rellipse.x * pe->cdir + r.frange( 0.0f, pe->extents[1] ) * pe->dir + rellipse.y * pe->odir; } } static void nv_particle_emmiter_hollow_sphere( const particle_emmiter_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 ); p[i].position = rellipse.x * pe->cdir + rellipse.y * pe->dir + rellipse.z * pe->odir; } } static void nv_particle_emmiter_hollow_cylindroid( const particle_emmiter_data* pe, particle* p, uint32 count ) { random& r = random::get(); 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] ), pe->precise ); p[i].position = rellipse.x * pe->cdir + r.frange( 0.0f, pe->extents[1] ) * pe->dir + rellipse.y * pe->odir; } } static void nv_particle_emmiter_hollow_ellipsoid( const particle_emmiter_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 ); p[i].position = rellipse.x * pe->cdir + rellipse.y * pe->dir + rellipse.z * pe->odir; } } struct nvpe_linear_force_data { nv::vec3 force_vector; bool average; }; static bool nv_particle_affector_linear_force_init( lua::table_guard* table, particle_affector_data* data ) { nvpe_linear_force_data* datap = reinterpret_cast( data->paramters ); datap->force_vector = table->get("force_vector", vec3() ); datap->average = table->get("average", false ); return true; } static void nv_particle_affector_linear_force( const particle_affector_data* data, particle* p, float factor, uint32 count ) { const nvpe_linear_force_data* datap = reinterpret_cast( data->paramters ); if ( datap->average ) { float norm_factor = nv::min( factor, 1.0f, p->lifetime ); for ( uint32 i = 0; i < count; ++i ) p[i].velocity = datap->force_vector * norm_factor + p[i].velocity * ( 1.0f - norm_factor ); } else { vec3 scvector = datap->force_vector * nv::min( factor, p->lifetime ); for ( uint32 i = 0; i < count; ++i ) p[i].velocity += scvector; } } struct nvpe_deflector_plane_data { nv::vec3 plane_point; nv::vec3 plane_normal; float bounce; float distance; }; static bool nv_particle_affector_deflector_plane_init( lua::table_guard* table, particle_affector_data* data ) { nvpe_deflector_plane_data* datap = reinterpret_cast( data->paramters ); datap->plane_point = table->get("plane_point", vec3() ); datap->plane_normal = table->get("plane_normal", vec3(0.0f,1.0f,0.0f) ); datap->plane_normal = normalize_safe( datap->plane_normal, vec3(0.0f,1.0f,0.0f) ); datap->bounce = table->get("bounce", 0.0f ); datap->distance = -math::dot( datap->plane_normal, datap->plane_point ) / sqrt( math::dot( datap->plane_normal, datap->plane_normal ) ); return true; } static void nv_particle_affector_deflector_plane( const particle_affector_data* data, particle* p, float factor, uint32 count ) { const nvpe_deflector_plane_data* datap = reinterpret_cast( data->paramters ); for ( uint32 i = 0; i < count; ++i ) { particle& pt = p[i]; vec3 direction = pt.velocity * nv::min( factor, p->lifetime ); if ( math::dot( datap->plane_normal, pt.position + direction ) + datap->distance <= 0.0f ) { float val = math::dot( datap->plane_normal, pt.position ) + datap->distance; if ( val > 0.0f ) { vec3 part_dir = direction * ( -val / math::dot( datap->plane_normal, direction ) ); pt.position = pt.position + part_dir + ( part_dir - direction ) * datap->bounce; pt.velocity = math::reflect( pt.velocity, datap->plane_normal ) * datap->bounce; } } } } struct nvpe_color_fader_data { nv::vec4 adjustment; }; static bool nv_particle_affector_color_fader_init( lua::table_guard* table, particle_affector_data* data ) { nvpe_color_fader_data* datap = reinterpret_cast( data->paramters ); datap->adjustment = table->get("adjustment", vec4() ); return true; } static void nv_particle_affector_color_fader( const particle_affector_data* data, particle* p, float factor, uint32 count ) { const nvpe_color_fader_data* datap = reinterpret_cast( data->paramters ); vec4 adjustment = datap->adjustment * nv::min( factor, p->lifetime ); for ( uint32 i = 0; i < count; ++i ) { p[i].color = math::clamp( p[i].color + adjustment, 0.0f, 1.0f ); } } struct nvpe_scaler_data { nv::vec2 adjustment; }; static bool nv_particle_affector_scaler_init( lua::table_guard* table, particle_affector_data* data ) { nvpe_scaler_data* datap = reinterpret_cast( data->paramters ); float rate = table->get("rate", 0.0f ); datap->adjustment = table->get("adjustment", vec2(rate,rate) ); return true; } static void nv_particle_affector_scaler( const particle_affector_data* data, particle* p, float factor, uint32 count ) { const nvpe_scaler_data* datap = reinterpret_cast( data->paramters ); vec2 adjustment = datap->adjustment * nv::min( factor, p->lifetime ); for ( uint32 i = 0; i < count; ++i ) { p[i].size = math::max( p[i].size + adjustment, vec2() ); } } void nv::particle_engine::load( lua::table_guard& table ) { shash64 id = table.get_string_hash_64( "id" ); if ( !id ) { NV_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.quota = table.get("quota", 1024 ); data.local = table.get("local_space", false ); data.accurate_facing = table.get("accurate_facing", false ); data.emmiter_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.common_up = math::normalize( table.get("common_up", vec3(1,0,0) ) ); data.common_dir = math::normalize( table.get("common_dir", vec3(0,1,0) ) ); vec2 def_size = table.get("size", vec2(0.1,0.1) ); uint32 elements = table.get_size(); for ( uint32 i = 0; i < elements; ++i ) { lua::table_guard element( table, i+1 ); const_string type = element.get_string( "type" ); const_string sub_type = element.get_string( "sub_type" ); if ( type == "emmiter" ) { if ( data.emmiter_count < MAX_PARTICLE_EMMITERS ) { particle_emmiter_data& edata = data.emmiters[ data.emmiter_count ]; auto emmiter_iter = m_emmiters.find( sub_type ); if ( emmiter_iter != m_emmiters.end() ) { edata.emmiter_func = emmiter_iter->second; } else { edata.emmiter_func = nv_particle_emmiter_point; NV_LOG_WARNING( "Unknown emmiter type in particle system! (", sub_type, ")" ); } edata.position = element.get("position", vec3() ); edata.extents = element.get("extents", vec3(1,1,1) ); edata.extents[0] = element.get("width", edata.extents[0] ); edata.extents[1] = element.get("depth", edata.extents[1] ); edata.extents[2] = element.get("height", edata.extents[2] ); edata.extents[0] = element.get("radius", edata.extents[0] ); edata.iextents = element.get("inner_extents", vec3() ); edata.iextents[0] = element.get("inner_width", edata.iextents[0] ); edata.iextents[1] = element.get("inner_depth", edata.iextents[1] ); edata.iextents[2] = element.get("inner_height", edata.iextents[2] ); edata.iextents[0] = element.get("inner_radius", edata.iextents[0] ); edata.hextents = 0.5f * edata.extents; edata.ihextents = 0.5f * edata.iextents; edata.precise = element.get("precise", false ); edata.square = element.get("square", true ); 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", def_size ); edata.size_min = element.get("size_min", size ); edata.size_max = element.get("size_max", size ); 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 = element.get("lifetime_min", lifetime ); edata.lifetime_max = element.get("lifetime_max", lifetime ); float duration = element.get("duration", 0.0f ); edata.duration_min = element.get("duration_min", duration ); edata.duration_max = element.get("duration_max", duration ); float repeat = element.get("repeat_delay", 0.0f ); edata.repeat_min = element.get("repeat_delay_min", repeat ); edata.repeat_max = element.get("repeat_delay_max", repeat ); edata.rate = element.get("rate", 1.0f ); edata.dir = math::normalize( element.get("direction", vec3(0,1,0) ) ); edata.odir = vec3( 0, 0, 1 ); if ( edata.dir != vec3( 0, 1, 0 ) && edata.dir != vec3( 0, -1, 0 ) ) edata.odir = math::normalize( math::cross( edata.dir, vec3( 0, 1, 0 ) ) ); edata.cdir = math::cross( edata.dir, edata.odir ); data.emmiter_count++; } else { NV_LOG_ERROR( "Too many emmiters (", MAX_PARTICLE_EMMITERS, " is MAX)!" ); } } else if ( type == "affector" ) { if ( data.affector_count < MAX_PARTICLE_AFFECTORS ) { particle_affector_data& adata = data.affectors[ data.affector_count ]; data.affector_count++; auto affector_iter = m_affectors.find( sub_type ); if ( affector_iter != m_affectors.end() ) { adata.process = affector_iter->second.process; if ( !affector_iter->second.init( &element, &adata ) ) { data.affector_count--; NV_LOG_WARNING( "Bad data passed to ", sub_type, " affector in particle system!" ); } } else { data.affector_count--; NV_LOG_WARNING( "Unknown affector type in particle system! (", sub_type, ")" ); } } else { NV_LOG_ERROR( "Too many affectors (", MAX_PARTICLE_AFFECTORS, " is MAX)!" ); } } else { NV_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 ); register_standard_emmiters(); register_standard_affectors(); } nv::particle_system nv::particle_engine::create_system( const string_view& id, particle_system_group group ) { particle_system_group_info* ginfo = m_groups.get( group ); if ( !ginfo ) return nv::particle_system(); 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->group = group; info->data = data; uint32 ecount = data->emmiter_count; for ( uint32 i = 0; i < ecount; ++i ) { info->emmiters[i].active = true; if ( data->emmiters[i].duration_max == 0.0f ) info->emmiters[i].pause = 0; else info->emmiters[i].pause = random::get().frange( data->emmiters[i].duration_min, data->emmiters[i].duration_max ); } info->count = 0; info->particles = new particle[ data->quota ]; ginfo->ref_counter++; return result; } void nv::particle_engine::prepare( particle_system_group group ) { particle_system_group_info* info = m_groups.get( group ); if ( info ) { info->count = 0; } } void nv::particle_engine::draw( particle_system_group group, const render_state& rs, const scene_state& ss ) { particle_system_group_info* info = m_groups.get( group ); if ( info ) { m_context->draw( nv::TRIANGLES, rs, ss, info->local ? m_program_local : m_program_world, info->vtx_array, info->count * 6, 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_device->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; } nv::particle_engine::~particle_engine() { clear(); m_device->release( m_program_world ); m_device->release( m_program_local ); } void nv::particle_engine::reset() { clear(); register_standard_emmiters(); register_standard_affectors(); } bool nv::particle_engine::loaded( const string_view& system_id ) { return m_names.find( system_id ) != m_names.end(); } void nv::particle_engine::clear() { while ( m_systems.size() > 0 ) release( m_systems.get_handle( 0 ) ); while ( m_groups.size() > 0 ) release( m_groups.get_handle( 0 ) ); m_emmiters.clear(); m_affectors.clear(); m_names.clear(); m_data.clear(); } void nv::particle_engine::release( particle_system system ) { particle_system_info* info = m_systems.get( system ); if ( info ) { particle_system_group_info* ginfo = m_groups.get( info->group ); if ( ginfo ) ginfo->ref_counter--; 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::render( particle_system system, const scene_state& s ) { particle_system_info* info = m_systems.get( system ); if ( info ) { generate_data( info, s ); } } void nv::particle_engine::update( particle_system system, float dtime ) { particle_system_info* info = m_systems.get( system ); if ( info ) { // while ( dtime > 0.2 ) // { // update_emmiters( info, 0.2 ); // destroy_particles( info, 0.2 ); // create_particles( info, 0.2 ); // update_particles( info, 0.2 ); // dtime -= 0.2; // } update_emmiters( info, dtime ); destroy_particles( info, dtime ); create_particles( info, dtime ); update_particles( info, dtime ); // generate_data( info ); } } void nv::particle_engine::set_texcoords( particle_system system, vec2 a, vec2 b ) { particle_system_info* info = m_systems.get( system ); if ( info ) { info->texcoords[0] = a; 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( 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; } void nv::particle_engine::destroy_particles( particle_system_info* info, float dtime ) { if ( info->count > 0 ) for ( sint32 i = sint32( info->count ) - 1; i >= 0; --i ) { particle& pinfo = info->particles[i]; pinfo.lifetime += dtime; if ( pinfo.lifetime >= pinfo.death ) { info->count--; swap( info->particles[i], info->particles[info->count] ); } } } void nv::particle_engine::create_particles( particle_system_info* info, float dtime ) { uint32 ecount = info->data->emmiter_count; if ( ecount == 0 ) return; random& r = random::get(); vec3 source; mat3 orient; bool local = info->data->local; // if ( !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]; if ( einfo.active ) { einfo.next -= dtime; float fperiod = 1.0f / edata.rate; while ( einfo.next < 0.0f ) { if ( info->count < info->data->quota-1 ) { particle& pinfo = info->particles[info->count]; edata.emmiter_func( &(info->data->emmiters[i]), &pinfo, 1 ); pinfo.position = vec3(); // if ( !local ) pinfo.position = orient * pinfo.position + source; pinfo.position+= edata.position; 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; float velocity = edata.velocity_min == edata.velocity_max ? edata.velocity_min : r.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() ); pinfo.tcoord_a = info->texcoords[0]; pinfo.tcoord_b = info->texcoords[1]; pinfo.velocity = edata.dir; if ( edata.angle > 0.0f ) { float emission_angle = math::radians( edata.angle ); float cos_theta = r.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() ); pinfo.velocity = orient * ( edata.odir * ( cos(phi) * sin_theta ) + edata.cdir * ( sin(phi)*sin_theta ) + edata.dir * cos_theta ); } pinfo.velocity *= velocity; info->count++; } einfo.next += fperiod; } } } } void nv::particle_engine::update_particles( particle_system_info* info, float dtime ) { if ( dtime <= 0.0f ) return; uint32 acount = info->data->affector_count; for ( uint32 i = 0; i < acount; ++i ) { const particle_affector_data* padata = &(info->data->affectors[i]); padata->process( padata, info->particles, dtime, info->count ); } for ( uint32 i = 0; i < info->count; ++i ) { particle& pdata = info->particles[i]; float factor = min( dtime, pdata.lifetime ); pdata.position += pdata.velocity * factor; } } void nv::particle_engine::update_emmiters( particle_system_info* info, float dtime ) { uint32 ecount = info->data->emmiter_count; if ( ecount == 0 ) return; random& r = random::get(); for ( uint32 i = 0; i < ecount; ++i ) { const auto& edata = info->data->emmiters[i]; auto& einfo = info->emmiters[i]; if ( einfo.pause > 0.0f ) { einfo.pause -= dtime; if ( einfo.pause == 0.0f ) einfo.pause = -0.001f; } if ( einfo.pause < 0.0f ) { if ( einfo.active ) { einfo.active = false; if ( edata.repeat_min > 0.0f ) einfo.pause += r.frange( edata.repeat_min, edata.repeat_max ); else einfo.pause = 0.0f; } else { einfo.active = true; einfo.pause += r.frange( edata.duration_min, edata.duration_max ); } } } } void nv::particle_engine::register_emmiter_type( const string_view& name, particle_emmiter_func func ) { m_emmiters[ name ] = func; } void nv::particle_engine::register_standard_emmiters() { register_emmiter_type( "point", nv_particle_emmiter_point ); register_emmiter_type( "box", nv_particle_emmiter_box ); register_emmiter_type( "cylinder", nv_particle_emmiter_cylinder ); register_emmiter_type( "sphere", nv_particle_emmiter_sphere ); register_emmiter_type( "cylindroid", nv_particle_emmiter_cylindroid ); register_emmiter_type( "ellipsoid", nv_particle_emmiter_ellipsoid ); register_emmiter_type( "hollow_cylinder", nv_particle_emmiter_hollow_cylinder ); register_emmiter_type( "hollow_sphere", nv_particle_emmiter_hollow_sphere ); register_emmiter_type( "hollow_cylindroid", nv_particle_emmiter_hollow_cylindroid ); register_emmiter_type( "hollow_ellipsoid", nv_particle_emmiter_hollow_ellipsoid ); } void nv::particle_engine::register_affector_type( const string_view& name, particle_affector_init_func init, particle_affector_func process ) { m_affectors[ name ].init = init; m_affectors[ name ].process = process; } 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() }; } void nv::particle_engine::register_standard_affectors() { register_affector_type( "linear_force", nv_particle_affector_linear_force_init, nv_particle_affector_linear_force ); register_affector_type( "deflector_plane", nv_particle_affector_deflector_plane_init, nv_particle_affector_deflector_plane ); register_affector_type( "color_fader", nv_particle_affector_color_fader_init, nv_particle_affector_color_fader ); register_affector_type( "scaler", nv_particle_affector_scaler_init, nv_particle_affector_scaler ); }