// Copyright (C) 2014-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_engine.hh" #include #include #include #include #include nv::hash_store< nv::shash64, nv::particle_emitter_func > nv::particle_engine::m_emitters; nv::hash_store< nv::shash64, nv::particle_affector_funcs > nv::particle_engine::m_affectors; nv::vector< nv::particle_emitter_func >* nv::particle_engine::m_debug_emitter_list; nv::vector< nv::particle_affector_func >* nv::particle_engine::m_debug_affector_list; nv::hash_map< nv::particle_emitter_func, nv::const_string >* nv::particle_engine::m_debug_emitter_names = nullptr; nv::hash_map< nv::particle_affector_func, nv::const_string >* nv::particle_engine::m_debug_affector_names = nullptr; nv::hash_map< nv::particle_affector_func, nv::type_entry* >* nv::particle_engine::m_debug_affector_types = nullptr; using namespace nv; static void nv_particle_emitter_point( random_base*, const particle_emitter_data*, particle* p, uint32 count ) { for ( uint32 i = 0; i < count; ++i ) { p[i].position = vec3(); } } 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 + rellipse.y * pe->odir; } } 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 + rsphere.y * pe->dir + rsphere.z * pe->odir; } } 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 + rellipse.y * pe->odir; } } 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 + rsphere.y * pe->dir + rsphere.z * pe->odir; } } 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], 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_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 + rellipse.y * pe->dir + rellipse.z * pe->odir; } } 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] ), 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_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 + rellipse.y * pe->dir + rellipse.z * pe->odir; } } struct nvpe_linear_force_data { nv::vec3 force_vector; bool average; }; NV_RTTI_DECLARE( nvpe_linear_force_data ) 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; }; NV_RTTI_DECLARE( nvpe_deflector_plane_data ) 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 ) / static_cast( math::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; }; NV_RTTI_DECLARE( nvpe_color_fader_data ) 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; }; NV_RTTI_DECLARE( nvpe_scaler_data ) 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() ); } } 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_emitter_list = new nv::vector< nv::particle_emitter_func >; m_debug_affector_list = new nv::vector< nv::particle_affector_func >; } register_standard_emitters(); register_standard_affectors(); } 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 ); info->group = group; info->data = data; uint32 ecount = data->emitter_count; for ( uint32 i = 0; i < ecount; ++i ) { info->emitters[i].active = true; if ( data->emitters[i].duration_max == 0.0f ) info->emitters[i].pause = 0; else info->emitters[i].pause = m_rng->frange( data->emitters[i].duration_min, data->emitters[i].duration_max ); } info->count = 0; info->particles = new particle[data->quota]; return result; } void nv::particle_engine::set_on_collide( particle_system system, const particle_on_collide_func& f ) { particle_system_info* info = m_systems.get( system ); if ( info ) info->on_collide = f; } nv::particle_system nv::particle_engine::create_system( resource< nv::particle_system_data > rdata, particle_group group ) { if ( auto data = rdata.lock() ) return create_system( &*data, group ); return {}; } 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 ); } nv::particle_engine::~particle_engine() { clear(); } void nv::particle_engine::reset() { clear(); register_standard_emitters( ); register_standard_affectors(); } void nv::particle_engine::clear() { while ( m_systems.size() > 0 ) release( m_systems.get_handle( 0 ) ); if ( m_pgm ) m_pgm->reset(); m_emitters.clear(); m_affectors.clear(); } void nv::particle_engine::release( particle_system system ) { particle_system_info* info = m_systems.get( system ); if ( info ) { m_pgm->unref( info->group ); delete[] info->particles; m_systems.destroy( system ); } } bool nv::particle_engine::is_finished( particle_system system ) { particle_system_info* info = m_systems.get( system ); if ( info ) { if ( info->count > 0 ) return false; for ( uint32 i = 0; i < info->data->emitter_count; ++i ) { const auto& edata = info->emitters[i]; if ( edata.active || edata.pause > 0.0f ) { return false; } } } return true; } void nv::particle_engine::release( particle_group group ) { m_pgm->release( group ); } void nv::particle_engine::render( particle_system system, const scene_state& s ) { particle_system_info* info = m_systems.get( system ); if ( info ) m_pgm->generate_data( array_view< particle >( info->particles, info->count ), info->data, info->group, s ); } void nv::particle_engine::update( particle_system system, transform model, float dtime ) { particle_system_info* info = m_systems.get( system ); if ( info ) { // while ( dtime > 0.2 ) // { // update_emitters( info, 0.2 ); // destroy_particles( info, 0.2 ); // create_particles( info, 0.2 ); // update_particles( info, 0.2 ); // dtime -= 0.2; // } update_emitters( info, dtime ); destroy_particles( info, dtime ); create_particles( info, model, 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::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, transform model, float dtime ) { uint32 ecount = info->data->emitter_count; if ( ecount == 0 ) return; for ( uint32 i = 0; i < ecount; ++i ) { const auto& edata = info->data->emitters[i]; auto& einfo = info->emitters[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.emitter_func( m_rng, &(info->data->emitters[i]), &pinfo, 1 ); pinfo.position = vec3(); pinfo.position+= edata.position; // if ( !local ) pinfo.position = pinfo.position * model; pinfo.color = 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 : 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 : 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 : m_rng->frange( edata.lifetime_min, edata.lifetime_max ) ); pinfo.rotation = m_rng->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 = m_rng->frange( cos( emission_angle ), 1.0f ); float sin_theta = sqrt(1.0f - cos_theta * cos_theta ); float phi = m_rng->frange( 0.0f, 2* math::pi() ); pinfo.velocity = model.get_orientation() * ( 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; } if ( info->on_collide ) { for ( uint32 i = 0; i < info->count; ++i ) { particle& pdata = info->particles[i]; if ( pdata.position.y <= 0.0f ) { info->on_collide( pdata.position, pdata.velocity ); pdata.death = pdata.lifetime; } } } } void nv::particle_engine::update_emitters( particle_system_info* info, float dtime ) { uint32 ecount = info->data->emitter_count; if ( ecount == 0 ) return; for ( uint32 i = 0; i < ecount; ++i ) { const auto& edata = info->data->emitters[i]; auto& einfo = info->emitters[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 += m_rng->frange( edata.repeat_min, edata.repeat_max ); else einfo.pause = 0.0f; } else { einfo.active = true; einfo.pause += m_rng->frange( edata.duration_min, edata.duration_max ); } } } } void nv::particle_engine::register_emitter_type( const string_view& name, particle_emitter_func func ) { if ( m_debug_emitter_names ) ( *m_debug_emitter_names )[func] = name; if ( m_debug_emitter_list ) ( *m_debug_emitter_list ).push_back( func ); m_emitters[ name ] = func; } void nv::particle_engine::register_standard_emitters() { register_emitter_type( "point", nv_particle_emitter_point ); register_emitter_type( "box", nv_particle_emitter_box ); register_emitter_type( "cylinder", nv_particle_emitter_cylinder ); register_emitter_type( "sphere", nv_particle_emitter_sphere ); register_emitter_type( "cylindroid", nv_particle_emitter_cylindroid ); register_emitter_type( "ellipsoid", nv_particle_emitter_ellipsoid ); register_emitter_type( "hollow_cylinder", nv_particle_emitter_hollow_cylinder ); register_emitter_type( "hollow_sphere", nv_particle_emitter_hollow_sphere ); register_emitter_type( "hollow_cylindroid", nv_particle_emitter_hollow_cylindroid ); register_emitter_type( "hollow_ellipsoid", nv_particle_emitter_hollow_ellipsoid ); } void nv::particle_engine::register_affector_type( const string_view& name, particle_affector_init_func init, particle_affector_func process ) { if ( m_debug_affector_names ) ( *m_debug_affector_names )[process] = name; if ( m_debug_affector_list ) ( *m_debug_affector_list ).push_back( process ); m_affectors[ name ].init = init; m_affectors[ name ].process = process; } nv::particle_emitter_func nv::particle_engine::get_emitter( shash64 emitter ) { auto emitter_iter = m_emitters.find( emitter ); if ( emitter_iter != m_emitters.end() ) { return emitter_iter->second; } return nullptr; } nv::particle_affector_funcs nv::particle_engine::get_affector( shash64 affector ) { auto affector_iter = m_affectors.find( affector ); if ( affector_iter != m_affectors.end() ) { return affector_iter->second; } return { nullptr, nullptr }; } nv::string_view nv::particle_engine::get_debug_name( particle_emitter_func f ) { auto i = m_debug_emitter_names->find( f ); if ( i != m_debug_emitter_names->end() ) return i->second; return {}; } nv::string_view nv::particle_engine::get_debug_name( particle_affector_func f ) { auto i = m_debug_affector_names->find( f ); if ( i != m_debug_affector_names->end() ) return i->second; return {}; } nv::particle_render_data nv::particle_engine::get_render_data( particle_group group ) { return m_pgm->get_render_data( group ); } 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 ); } 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() .value( "PS_POINT", sint32( particle_orientation::POINT ), "Point" ) .value( "PS_ORIENTED", sint32( particle_orientation::ORIENTED ), "Oriented" ) .value( "PS_ORIENTED_COMMON", sint32( particle_orientation::ORIENTED_COMMON ), "Oriented Common" ) .value( "PS_PERPENDICULAR", sint32( particle_orientation::PERPENDICULAR ), "Perpendicular" ) .value( "PS_PERPENDICULAR_COMMON", sint32( particle_orientation::PERPENDICULAR_COMMON ), "Perpendicular Common" ) ; db->create_type() .value( "PS_CENTER", sint32( particle_origin::CENTER ), "Center" ) .value( "PS_TOP_LEFT", sint32( particle_origin::TOP_LEFT ), "Top left" ) .value( "PS_TOP_CENTER", sint32( particle_origin::TOP_CENTER ), "Top center" ) .value( "PS_TOP_RIGHT", sint32( particle_origin::TOP_RIGHT ), "Top right" ) .value( "PS_CENTER_LEFT", sint32( particle_origin::CENTER_LEFT ), "Center left" ) .value( "PS_CENTER_RIGHT", sint32( particle_origin::CENTER_RIGHT ), "Center right" ) .value( "PS_BOTTOM_LEFT", sint32( particle_origin::BOTTOM_LEFT ), "Bottom left" ) .value( "PS_BOTTOM_CENTER", sint32( particle_origin::BOTTOM_CENTER ), "Bottom center" ) .value( "PS_BOTTOM_RIGHT", sint32( particle_origin::BOTTOM_RIGHT ), "Bottom right" ) ; 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; // compile_affector( ... ) in lua, returns array of bytecode // function is a normal bytecode runner! (*m_debug_affector_types)[ nv_particle_affector_linear_force ] = db->create_type( "linear force" ) .field( "force_vector", &nvpe_linear_force_data::force_vector ) .field( "average", &nvpe_linear_force_data::average ) .get(); ( *m_debug_affector_types )[nv_particle_affector_deflector_plane] = db->create_type( "deflector plane" ) .field( "plane_point", &nvpe_deflector_plane_data::plane_point ) .field( "plane_normal", &nvpe_deflector_plane_data::plane_normal ) .field( "bounce", &nvpe_deflector_plane_data::bounce ) .field( "distance", &nvpe_deflector_plane_data::distance ) .get(); ( *m_debug_affector_types )[nv_particle_affector_color_fader] = db->create_type( "color fader" ) .field( "adjustment", & nvpe_color_fader_data::adjustment ) .get(); ( *m_debug_affector_types )[nv_particle_affector_scaler] = db->create_type( "scaler" ) .field( "adjustment", & nvpe_scaler_data::adjustment ) .get(); } }