snake5 icon

particle system editor

snake5 | PRO | 10/06/15 08:26:27 AM UTC | 0 ⭐ | 846 👁️ | Never ⏰ | []
C++ |

33.96 KB

|

None

|

0 👍

/

0 👎

 
 
#include <engine.hpp>
#include <enganim.hpp>
#include <edgui.hpp>
#include "edcomui.hpp"
 
 
struct EDGUIMainFrame* g_UIFrame;
SceneHandle g_EdScene;
struct EdParticleSystem* g_EdPS;
struct EdDualGraph* g_UIGraphEd;
struct EDGUISDTexPicker* g_UITexPicker;
struct EDGUIShaderPicker* g_UIShaderPicker;
struct EDGUIMeshPicker* g_UIMeshPicker;
struct EDGUIPSOpenPicker* g_UIPSOpenPicker;
struct EDGUIPSSavePicker* g_UIPSSavePicker;
struct EDGUIPSISFXPicker* g_UIPSISFXPicker;
 
 
 
struct EDGUIShaderPicker : EDGUIRsrcPicker
{
    EDGUIShaderPicker()
    {
        Reload();
    }
    void Reload()
    {
        LOG << "Reloading shaders";
        m_options.clear();
        DirectoryIterator tdi( "shaders" );
        while( tdi.Next() )
        {
            StringView fn = tdi.Name();
            LOG << fn;
            if( !tdi.IsDirectory() )
            {
                if( fn.ends_with( ".shd" ) && fn.starts_with( "mtl_" ) )
                {
                    m_options.push_back( fn.part( 4, fn.size() - 8 ) );
                }
            }
        }
        _Search( m_searchString );
    }
};
 
 
struct EDGUIPSPicker : EDGUIRsrcPicker, IDirEntryHandler
{
    EDGUIPSPicker(){ Reload(); }
    void Reload()
    {
        LOG << "Reloading particle systems";
        m_options.clear();
        FS_IterateDirectory( "psys", this );
        _Search( m_searchString );
    }
    bool HandleDirEntry( const StringView& loc, const StringView& name, bool isdir )
    {
        LOG << "[P]: " << name;
        if( !isdir && name.ends_with( ".psy" ) )
        {
            m_options.push_back( name.part( 0, name.size() - 4 ) );
        }
        return true;
    }
    virtual void _OnChangeZoom()
    {
        EDGUIRsrcPicker::_OnChangeZoom();
        m_itemHeight /= 4;
    }
    
    virtual int OnEvent( EDGUIEvent* e )
    {
        if( e->type == EDGUI_EVENT_PROPCHANGE && e->target == &m_confirm )
        {
            if( m_confirm.m_value == 1 )
            {
                EDGUIRsrcPicker::_OnPickResource();
            }
            return 1;
        }
        return EDGUIRsrcPicker::OnEvent( e );
    }
    
    EDGUIQuestion m_confirm;
};
 
struct EDGUIPSOpenPicker : EDGUIPSPicker
{
    EDGUIPSOpenPicker()
    {
        caption = "Pick a particle system to open";
        m_confirm.caption = "Do you really want to open the particle system? All unsaved changes will be lost!";
    }
    virtual void _OnPickResource()
    {
        m_confirm.Open( this );
        m_frame->Add( &m_confirm );
    }
};
 
struct EDGUIPSSavePicker : EDGUIPSPicker
{
    EDGUIPSSavePicker()
    {
        caption = "Pick a particle system to save or write the name";
        m_confirm.caption = "Do you really want to overwrite the particle system?";
    }
    virtual void _OnPickResource()
    {
        if( m_options.find_first_at( m_pickedOption ) == NOT_FOUND )
            EDGUIRsrcPicker::_OnPickResource();
        else
        {
            m_confirm.Open( this );
            m_frame->Add( &m_confirm );
        }
    }
    virtual void _OnConfirm()
    {
        _OnPickResource();
    }
};
 
struct EDGUIPSISFXPicker : EDGUIRsrcPicker
{
    EDGUIPSISFXPicker()
    {
        caption = "Pick a particle intersection effect";
        Reload();
    }
    void Reload()
    {
        LOG << "Reloading particle intersection effects";
        
        Name2FXID.clear();
        FXID2Name.clear();
        m_options.clear();
        
        String isfx_data;
        if( FS_LoadTextFile( "editor/psys_isfx.txt", isfx_data ) == false )
        {
            LOG_ERROR << LOG_DATE << "  Failed to load editor/psys_isfx.txt";
            _Search( m_searchString );
            return;
        }
        ConfigReader cr( isfx_data );
        StringView key, value;
        while( cr.Read( key, value ) )
        {
            int id = String_ParseInt( key );
            char bfr[ 32 ];
            sgrx_snprintf( bfr, 32, "%d: ", id );
            String name = bfr;
            name.append( value );
            
            Name2FXID.set( name, id );
            FXID2Name.set( id, name );
            m_options.push_back( name );
        }
        
        LOG << "Loaded " << m_options.size() << " effects!";
        
        _Search( m_searchString );
    }
    
    static int GetFXIDFromName( const StringView& name )
    {
        int* ptr = Name2FXID.getptr( name );
        if( ptr )
            return *ptr;
        return String_ParseInt( name.until( ":" ) );
    }
    static String GetNameFromFXID( int id )
    {
        String* ptr = FXID2Name.getptr( id );
        if( ptr )
            return *ptr;
        char bfr[ 32 ];
        sgrx_snprintf( bfr, 32, "%d: --unknown--" );
        return bfr;
    }
    
    static HashTable< String, int > Name2FXID;
    static HashTable< int, String > FXID2Name;
};
HashTable< String, int > EDGUIPSISFXPicker::Name2FXID;
HashTable< int, String > EDGUIPSISFXPicker::FXID2Name;
 
 
struct EdDualGraph : EDGUIItem
{
    EdDualGraph() :
        m_mouseX(0), m_mouseY(0),
        m_A(false), m_B(false),
        m_data(NULL), m_size(0)
    {}
    
    int OnEvent( EDGUIEvent* e )
    {
        switch( e->type )
        {
        case EDGUI_EVENT_MOUSEMOVE:
            m_mouseX = e->mouse.x;
            m_mouseY = e->mouse.y;
            PaintGraph();
            break;
        case EDGUI_EVENT_BTNDOWN:
            if( e->mouse.button == 0 ) m_A = true;
            if( e->mouse.button == 1 ) m_B = true;
            PaintGraph();
            break;
        case EDGUI_EVENT_BTNUP:
            if( e->mouse.button == 0 ) m_A = false;
            if( e->mouse.button == 1 ) m_B = false;
            PaintGraph();
            break;
        case EDGUI_EVENT_LAYOUT:
            x0 = e->layout.x0 + 8;
            y0 = e->layout.y0 + 8;
            x1 = e->layout.x1 - 8;
            y1 = e->layout.y1 - 8;
            return 1;
        case EDGUI_EVENT_PAINT:
            EDGUIItem::OnEvent( e );
            if( m_data )
            {
                BatchRenderer& br = GR2D_GetBatchRenderer();
                br.Reset().Col( 0, 0.5f );
                br.Quad( x0, y0, x1, y1 );
                if( m_size > 1 )
                {
                    br.Col( 0.1f, 0.7f, 0 );
                    br.SetPrimitiveType( PT_LineStrip );
                    for( size_t i = 0; i < m_size; ++i )
                    {
                        float x = x0 + ( x1 - x0 ) * i / (float) ( m_size - 1 );
                        float y = y1 + ( y0 - y1 ) * m_data[ i ].x;
                        br.Pos( x, y );
                    }
                    br.Col( 0.7f, 0.1f, 0 );
                    br.SetPrimitiveType( PT_LineStrip );
                    for( size_t i = 0; i < m_size; ++i )
                    {
                        float x = x0 + ( x1 - x0 ) * i / (float) ( m_size - 1 );
                        float y = y1 + ( y0 - y1 ) * m_data[ i ].y;
                        br.Pos( x, y );
                    }
                    br.Flush();
                }
            }
            return 1;
        }
        return EDGUIItem::OnEvent( e );
    }
    
    void PaintGraph()
    {
        if( !m_data || !m_size || x1 <= x0 || y1 <= y0 )
            return;
        
        // convert mouse position into graph position
        float x = float( m_mouseX - x0 ) / float( x1 - x0 ) * ( m_size - 1 );
        float y = clamp( float( m_mouseY - y1 ) / float( y0 - y1 ), 0, 1 );
        
        int at = round( clamp( x, 0, m_size - 1 ) );
        if( m_A ) m_data[ at ].x = y;
        if( m_B ) m_data[ at ].y = y;
    }
    
    void SetCurve( Vec2* p, size_t s )
    {
        m_data = p;
        m_size = s;
    }
    void UnsetCurve(){ SetCurve( NULL, 0 ); }
    
    int m_mouseX;
    int m_mouseY;
    bool m_A;
    bool m_B;
    
    Vec2* m_data;
    size_t m_size;
};
 
struct EdEmitCurve : EDGUIGroup
{
    EdEmitCurve( bool open, const StringView& name = StringView() ) :
        EDGUIGroup( open, name ),
        curveSize( 1, 1, 1024 ),
        randomValDvg( V2(0), 2, V2(-100), V2(100) ),
        valueRange( V2(0,1), 2, V2(-1000), V2(1000) )
    {
        editBtn.caption = "Edit curve";
        curveSize.caption = "# of sample points";
        randomValDvg.caption = "Random value/divergence";
        valueRange.caption = "Min./max. values";
        
        Add( &editBtn );
        Add( &curveSize );
        Add( &randomValDvg );
        Add( &valueRange );
    }
    
    void SetValue( ParticleSystem::Emitter::Curve& curve )
    {
        m_curve = &curve;
        
        curveSize.SetValue( curve.values.size() );
        randomValDvg.SetValue( curve.randomValDvg );
        valueRange.SetValue( curve.valueRange );
    }
    
    int OnEvent( EDGUIEvent* e )
    {
        switch( e->type )
        {
        case EDGUI_EVENT_PROPCHANGE:
            if( e->target == &curveSize )
            {
                int ncs = curveSize.m_value;
                if( ncs < 1 ) ncs = 1;
                if( ncs > 1024 ) ncs = 1024;
                m_curve->values.resize( ncs );
            }
            break;
        case EDGUI_EVENT_PROPEDIT:
            if( e->target == &randomValDvg ) m_curve->randomValDvg = randomValDvg.m_value;
            if( e->target == &valueRange ) m_curve->valueRange = valueRange.m_value;
            break;
        case EDGUI_EVENT_BTNCLICK:
            if( e->target == &editBtn )
            {
                g_UIGraphEd->SetCurve( m_curve->values.data(), m_curve->values.size() );
            }
            break;
        case EDGUI_EVENT_PAINT:
            EDGUIGroup::OnEvent( e );
            {
                int rx0 = ( x0 + x1 ) / 2 + 2;
                int ry0 = y0 + 2;
                int rx1 = x1 - 2;
                int ry1 = y0 + EDGUI_THEME_GROUP_HEIGHT - 2;
                
                BatchRenderer& br = GR2D_GetBatchRenderer();
                br.Reset().Col( 0, 0.5f );
                br.Quad( rx0, ry0, rx1, ry1 );
                if( m_curve->values.size() > 1 )
                {
                    br.Col( 0.1f, 0.7f, 0 );
                    br.SetPrimitiveType( PT_LineStrip );
                    for( size_t i = 0; i < m_curve->values.size(); ++i )
                    {
                        float x = rx0 + ( rx1 - rx0 ) * i / (float) ( m_curve->values.size() - 1 );
                        float y = ry1 + ( ry0 - ry1 ) * m_curve->values[ i ].x;
                        br.Pos( x, y );
                    }
                    br.Col( 0.7f, 0.1f, 0 );
                    br.SetPrimitiveType( PT_LineStrip );
                    for( size_t i = 0; i < m_curve->values.size(); ++i )
                    {
                        float x = rx0 + ( rx1 - rx0 ) * i / (float) ( m_curve->values.size() - 1 );
                        float y = ry1 + ( ry0 - ry1 ) * m_curve->values[ i ].y;
                        br.Pos( x, y );
                    }
                    br.Flush();
                }
            }
            return 1;
        }
        return EDGUIGroup::OnEvent( e );
    }
    
    ParticleSystem::Emitter::Curve* m_curve;
    
    EDGUIButton editBtn;
    EDGUIPropInt curveSize;
    EDGUIPropVec2 randomValDvg;
    EDGUIPropVec2 valueRange;
};
 
#define MAX_PARTICLES 10000
#define MAX_TIME 120.0f
#define MAX_POS 8192.0f
#define MAX_NORMAL 100.0f
#define MAX_VEL 100.0f
 
static String GetParticleTexName( const TextureHandle& tex )
{
    if( !tex )
        return String();
    StringView oname = tex->m_key;
    if( oname.starts_with( "textures/particles/" ) && oname.ends_with( ".png" ) )
        return oname.part( sizeof("textures/particles/")-1, oname.size() - (sizeof("textures/particles/.png")-1) );
    // cannot resolve:
    return oname;
}
 
static TextureHandle GetParticleTexHandle( const StringView& sv )
{
    if( !sv )
        return TextureHandle();
    char bfr[ 1024 ] = {0};
    sgrx_snprintf( bfr, 1024, "textures/particles/%.*s.png", TMIN( 980, (int) sv.size() ), sv.data() );
    return GR_GetTexture( bfr );
}
 
struct EdEmitter : EDGUILayoutRow
{
    EdEmitter() :
        grpSpawn( false, "Spawn properties" ),
        spawn_MaxCount( 0, 0, MAX_PARTICLES ),
        spawn_Count( 0, 0, MAX_PARTICLES ),
        spawn_CountExt( 0, 0, MAX_PARTICLES ),
        spawn_TimeExt( V2(0), 2, V2(0), V2(MAX_TIME) ),
        
        grpPosition( false, "Initial position" ),
        create_Pos( V3(0), 2, V3(-MAX_POS), V3(MAX_POS) ),
        create_PosBox( V3(0), 2, V3(0), V3(MAX_POS) ),
        create_PosRadius( 0, 2, 0, MAX_POS ),
        
        grpVelocity( false, "Initial velocity" ),
        create_VelMicroDir( V3(0), 2, V3(-MAX_NORMAL), V3(MAX_NORMAL) ),
        create_VelMicroDvg( 0, 2, 0, 1 ),
        create_VelMicroDistExt( V2(0), 2, V2(-MAX_VEL), V2(MAX_VEL) ),
        create_VelMacroDir( V3(0), 2, V3(-MAX_NORMAL), V3(MAX_NORMAL) ),
        create_VelMacroDvg( 0, 2, 0, 1 ),
        create_VelMacroDistExt( V2(0), 2, V2(-MAX_VEL), V2(MAX_VEL) ),
        create_VelCluster( 0, 0, MAX_PARTICLES ),
        create_VelClusterExt( 0, 0, MAX_PARTICLES ),
        
        grpMisc( false, "Misc. properties" ),
        create_LifetimeExt( V2(0), 2, V2(0), V2(MAX_TIME) ),
        create_AngleDirDvg( V2(0), 2, V2(0,0), V2(360,180) ),
        create_AngleVelDvg( V2(0), 2, V2(-1000), V2(1000) ),
        tick_AngleAcc( 0, 2, -10000, 10000 ),
        tick_GravityMult( 1, 2, 0, 100 ),
        absolute( true ),
        
        grpIsect( false, "Intersection" ),
        isect_Limit( 0, 0, MAX_PARTICLES ),
        isect_Friction( 0, 2, 0, 1 ),
        isect_Bounce( 0, 2, 0, 1 ),
        isect_FXID( g_UIPSISFXPicker, EDGUIPSISFXPicker::GetNameFromFXID( 0 ) ),
        isect_FXChance( 0, 2, 0, 1 ),
        isect_Remove( false ),
        
        grpSizeColor( false, "Size/color curves" ),
        curve_Size( false, "Size" ),
        curve_ColorHue( false, "Color - hue" ),
        curve_ColorSat( false, "Color - saturation" ),
        curve_ColorVal( false, "Color - value" ),
        curve_Opacity( false, "Opacity" ),
        
        grpRender( true, "Rendering data" ),
        render_Texture0( g_UITexPicker, "bullet" ),
        render_Texture1( g_UITexPicker, "" ),
        render_Texture2( g_UITexPicker, "" ),
        render_Texture3( g_UITexPicker, "" ),
        render_Shader( g_UIShaderPicker, "particle" ),
        render_Additive( false ),
        render_Stretch( false ),
        
        m_psys( NULL ), m_emitter( NULL )
    {
        spawn_MaxCount.caption = "Max. particle count";
        spawn_Count.caption = "# of particles to spawn";
        spawn_CountExt.caption = "# + random";
        spawn_TimeExt.caption = "Spawn time / rand.ext.";
        
        create_Pos.caption = "Origin";
        create_PosBox.caption = "Random extents";
        create_PosRadius.caption = "Random radius";
        
        create_VelMicroDir.caption = "V/micro/direction";
        create_VelMicroDvg.caption = "V/micro/divergence";
        create_VelMicroDistExt.caption = "V/micro/dist+rand.ext.";
        create_VelMacroDir.caption = "V/macro/direction";
        create_VelMacroDvg.caption = "V/macro/divergence";
        create_VelMacroDistExt.caption = "V/macro/dist+rand.ext.";
        create_VelCluster.caption = "V - cluster size";
        create_VelClusterExt.caption = "V - cluster rand.ext.";
        
        create_LifetimeExt.caption = "Lifetime + rand.ext.";
        create_AngleDirDvg.caption = "Angle / divergence";
        create_AngleVelDvg.caption = "Angular velocity / divergence";
        tick_AngleAcc.caption = "Angle - acceleration";
        tick_GravityMult.caption = "Gravity mulitplier";
        absolute.caption = "World space";
        
        isect_Limit.caption = "Limit";
        isect_Friction.caption = "Friction";
        isect_Bounce.caption = "Bounce";
        isect_FXID.caption = "Hit FX";
        isect_FXChance.caption = "Hit FX chance";
        isect_Remove.caption = "Remove on hit";
        
        render_Texture0.caption = "Texture #1";
        render_Texture1.caption = "Texture #2";
        render_Texture2.caption = "Texture #3";
        render_Texture3.caption = "Texture #4";
        render_Shader.caption = "Shader";
        render_Additive.caption = "Is additive+unlit?";
        render_Stretch.caption = "Is velocity-stretched?";
        
        grpSpawn.Add( &spawn_MaxCount );
        grpSpawn.Add( &spawn_Count );
        grpSpawn.Add( &spawn_CountExt );
        grpSpawn.Add( &spawn_TimeExt );
        Add( &grpSpawn );
        
        grpPosition.Add( &create_Pos );
        grpPosition.Add( &create_PosBox );
        grpPosition.Add( &create_PosRadius );
        Add( &grpPosition );
        
        grpVelocity.Add( &create_VelMicroDir );
        grpVelocity.Add( &create_VelMicroDvg );
        grpVelocity.Add( &create_VelMicroDistExt );
        grpVelocity.Add( &create_VelMacroDir );
        grpVelocity.Add( &create_VelMacroDvg );
        grpVelocity.Add( &create_VelMacroDistExt );
        grpVelocity.Add( &create_VelCluster );
        grpVelocity.Add( &create_VelClusterExt );
        Add( &grpVelocity );
        
        grpMisc.Add( &create_LifetimeExt );
        grpMisc.Add( &create_AngleDirDvg );
        grpMisc.Add( &create_AngleVelDvg );
        grpMisc.Add( &tick_AngleAcc );
        grpMisc.Add( &tick_GravityMult );
        grpMisc.Add( &absolute );
        Add( &grpMisc );
        
        grpIsect.Add( &isect_Limit );
        grpIsect.Add( &isect_Friction );
        grpIsect.Add( &isect_Bounce );
        grpIsect.Add( &isect_FXID );
        grpIsect.Add( &isect_FXChance );
        grpIsect.Add( &isect_Remove );
        Add( &grpIsect );
        
        grpSizeColor.Add( &curve_Size );
        grpSizeColor.Add( &curve_ColorHue );
        grpSizeColor.Add( &curve_ColorSat );
        grpSizeColor.Add( &curve_ColorVal );
        grpSizeColor.Add( &curve_Opacity );
        Add( &grpSizeColor );
        
        grpRender.Add( &render_Texture0 );
        grpRender.Add( &render_Texture1 );
        grpRender.Add( &render_Texture2 );
        grpRender.Add( &render_Texture3 );
        grpRender.Add( &render_Shader );
        grpRender.Add( &render_Additive );
        grpRender.Add( &render_Stretch );
        Add( &grpRender );
    }
    
    int OnEvent( EDGUIEvent* e )
    {
        switch( e->type )
        {
        case EDGUI_EVENT_PROPEDIT:
            if( e->target == &spawn_MaxCount ){ m_emitter->spawn_MaxCount = spawn_MaxCount.m_value; _U(false); }
            if( e->target == &spawn_Count ){ m_emitter->spawn_Count = spawn_Count.m_value; _U(false); }
            if( e->target == &spawn_CountExt ){ m_emitter->spawn_CountExt = spawn_CountExt.m_value; _U(false); }
            if( e->target == &spawn_TimeExt ){ m_emitter->spawn_TimeExt = spawn_TimeExt.m_value; _U(false); }
            
            if( e->target == &create_Pos ){ m_emitter->create_Pos = create_Pos.m_value; _U(false); }
            if( e->target == &create_PosBox ){ m_emitter->create_PosBox = create_PosBox.m_value; _U(false); }
            if( e->target == &create_PosRadius ){ m_emitter->create_PosRadius = create_PosRadius.m_value; _U(false); }
            
            if( e->target == &create_VelMicroDir ){ m_emitter->create_VelMicroDir = create_VelMicroDir.m_value; _U(false); }
            if( e->target == &create_VelMicroDvg ){ m_emitter->create_VelMicroDvg = create_VelMicroDvg.m_value; _U(false); }
            if( e->target == &create_VelMicroDistExt ){ m_emitter->create_VelMicroDistExt = create_VelMicroDistExt.m_value; _U(false); }
            if( e->target == &create_VelMacroDir ){ m_emitter->create_VelMacroDir = create_VelMacroDir.m_value; _U(false); }
            if( e->target == &create_VelMacroDvg ){ m_emitter->create_VelMacroDvg = create_VelMacroDvg.m_value; _U(false); }
            if( e->target == &create_VelMacroDistExt ){ m_emitter->create_VelMacroDistExt = create_VelMacroDistExt.m_value; _U(false); }
            if( e->target == &create_VelCluster ){ m_emitter->create_VelCluster = create_VelCluster.m_value; _U(false); }
            if( e->target == &create_VelClusterExt ){ m_emitter->create_VelClusterExt = create_VelClusterExt.m_value; _U(false); }
            
            if( e->target == &create_LifetimeExt ){ m_emitter->create_LifetimeExt = create_LifetimeExt.m_value; _U(false); }
            if( e->target == &create_AngleDirDvg ){ m_emitter->create_AngleDirDvg = DEG2RAD( create_AngleDirDvg.m_value ); _U(false); }
            if( e->target == &create_AngleVelDvg ){ m_emitter->create_AngleVelDvg = DEG2RAD( create_AngleVelDvg.m_value ); _U(false); }
            if( e->target == &tick_AngleAcc ){ m_emitter->tick_AngleAcc = DEG2RAD( tick_AngleAcc.m_value ); _U(false); }
            if( e->target == &tick_GravityMult ){ m_emitter->tick_GravityMult = tick_GravityMult.m_value; _U(false); }
            if( e->target == &absolute ){ m_emitter->absolute = absolute.m_value; _U(false); }
            
            if( e->target == &isect_Limit ){ m_emitter->isect_Limit = isect_Limit.m_value; _U(false); }
            if( e->target == &isect_Friction ){ m_emitter->isect_Friction = isect_Friction.m_value; _U(false); }
            if( e->target == &isect_Bounce ){ m_emitter->isect_Bounce = isect_Bounce.m_value; _U(false); }
            if( e->target == &isect_FXID ){ m_emitter->isect_FXID = EDGUIPSISFXPicker::GetFXIDFromName( isect_FXID.m_value ); _U(false); }
            if( e->target == &isect_FXChance ){ m_emitter->isect_FXChance = isect_FXChance.m_value; _U(false); }
            if( e->target == &isect_Remove ){ m_emitter->isect_Remove = isect_Remove.m_value; _U(false); }
            
            if( e->target == &render_Texture0 ){ m_emitter->render_Textures[0] = GetParticleTexHandle( render_Texture0.m_value ); _U(true); }
            if( e->target == &render_Texture1 ){ m_emitter->render_Textures[1] = GetParticleTexHandle( render_Texture1.m_value ); _U(true); }
            if( e->target == &render_Texture2 ){ m_emitter->render_Textures[2] = GetParticleTexHandle( render_Texture2.m_value ); _U(true); }
            if( e->target == &render_Texture3 ){ m_emitter->render_Textures[3] = GetParticleTexHandle( render_Texture3.m_value ); _U(true); }
            if( e->target == &render_Shader ){ m_emitter->render_Shader = render_Shader.m_value; _U(true); }
            if( e->target == &render_Additive ){ m_emitter->render_Additive = render_Additive.m_value; _U(true); }
            if( e->target == &render_Stretch ){ m_emitter->render_Stretch = render_Stretch.m_value; _U(true); }
            break;
        }
        return EDGUILayoutRow::OnEvent( e );
    }
    void _U( bool renderupdate )
    {
        if( renderupdate )
            m_psys->OnRenderUpdate();
    }
    
    void Prepare( ParticleSystem* PS, size_t emid )
    {
        m_psys = PS;
        m_emitter = &PS->emitters[ emid ];
        
        spawn_MaxCount.SetValue( m_emitter->spawn_MaxCount );
        spawn_Count.SetValue( m_emitter->spawn_Count );
        spawn_CountExt.SetValue( m_emitter->spawn_CountExt );
        spawn_TimeExt.SetValue( m_emitter->spawn_TimeExt );
        
        create_Pos.SetValue( m_emitter->create_Pos );
        create_PosBox.SetValue( m_emitter->create_PosBox );
        create_PosRadius.SetValue( m_emitter->create_PosRadius );
    
        create_VelMicroDir.SetValue( m_emitter->create_VelMicroDir );
        create_VelMicroDvg.SetValue( m_emitter->create_VelMicroDvg );
        create_VelMicroDistExt.SetValue( m_emitter->create_VelMicroDistExt );
        create_VelMacroDir.SetValue( m_emitter->create_VelMacroDir );
        create_VelMacroDvg.SetValue( m_emitter->create_VelMacroDvg );
        create_VelMacroDistExt.SetValue( m_emitter->create_VelMacroDistExt );
        create_VelCluster.SetValue( m_emitter->create_VelCluster );
        create_VelClusterExt.SetValue( m_emitter->create_VelClusterExt );
        
        create_LifetimeExt.SetValue( m_emitter->create_LifetimeExt );
        create_AngleDirDvg.SetValue( RAD2DEG( m_emitter->create_AngleDirDvg ) );
        create_AngleVelDvg.SetValue( RAD2DEG( m_emitter->create_AngleVelDvg ) );
        tick_AngleAcc.SetValue( RAD2DEG( m_emitter->tick_AngleAcc ) );
        tick_GravityMult.SetValue( m_emitter->tick_GravityMult );
        absolute.SetValue( m_emitter->absolute );
        
        isect_Limit.SetValue( m_emitter->isect_Limit );
        isect_Friction.SetValue( m_emitter->isect_Friction );
        isect_Bounce.SetValue( m_emitter->isect_Bounce );
        isect_FXID.SetValue( EDGUIPSISFXPicker::GetNameFromFXID( m_emitter->isect_FXID ) );
        isect_FXChance.SetValue( m_emitter->isect_FXChance );
        isect_Remove.SetValue( m_emitter->isect_Remove );
        
        curve_Size.SetValue( m_emitter->curve_Size );
        curve_ColorHue.SetValue( m_emitter->curve_ColorHue );
        curve_ColorSat.SetValue( m_emitter->curve_ColorSat );
        curve_ColorVal.SetValue( m_emitter->curve_ColorVal );
        curve_Opacity.SetValue( m_emitter->curve_Opacity );
        
        render_Texture0.SetValue( GetParticleTexName( m_emitter->render_Textures[0] ) );
        render_Texture1.SetValue( GetParticleTexName( m_emitter->render_Textures[1] ) );
        render_Texture2.SetValue( GetParticleTexName( m_emitter->render_Textures[2] ) );
        render_Texture3.SetValue( GetParticleTexName( m_emitter->render_Textures[3] ) );
        render_Shader.SetValue( m_emitter->render_Shader );
        render_Additive.SetValue( m_emitter->render_Additive );
        render_Stretch.SetValue( m_emitter->render_Stretch );
    }
    
    EDGUIGroup grpSpawn;
    EDGUIPropInt spawn_MaxCount;
    EDGUIPropInt spawn_Count;
    EDGUIPropInt spawn_CountExt;
    EDGUIPropVec2 spawn_TimeExt;
    
    EDGUIGroup grpPosition;
    EDGUIPropVec3 create_Pos;
    EDGUIPropVec3 create_PosBox;
    EDGUIPropFloat create_PosRadius;
    
    EDGUIGroup grpVelocity;
    EDGUIPropVec3 create_VelMicroDir;
    EDGUIPropFloat create_VelMicroDvg;
    EDGUIPropVec2 create_VelMicroDistExt;
    EDGUIPropVec3 create_VelMacroDir;
    EDGUIPropFloat create_VelMacroDvg;
    EDGUIPropVec2 create_VelMacroDistExt;
    EDGUIPropInt create_VelCluster;
    EDGUIPropInt create_VelClusterExt;
    
    EDGUIGroup grpMisc;
    EDGUIPropVec2 create_LifetimeExt;
    EDGUIPropVec2 create_AngleDirDvg;
    EDGUIPropVec2 create_AngleVelDvg;
    EDGUIPropFloat tick_AngleAcc;
    EDGUIPropFloat tick_GravityMult;
    EDGUIPropBool absolute;
    
    EDGUIGroup grpIsect;
    EDGUIPropInt isect_Limit;
    EDGUIPropFloat isect_Friction;
    EDGUIPropFloat isect_Bounce;
    EDGUIPropRsrc isect_FXID;
    EDGUIPropFloat isect_FXChance;
    EDGUIPropBool isect_Remove;
    
    EDGUIGroup grpSizeColor;
    EdEmitCurve curve_Size;
    EdEmitCurve curve_ColorHue;
    EdEmitCurve curve_ColorSat;
    EdEmitCurve curve_ColorVal;
    EdEmitCurve curve_Opacity;
    
    EDGUIGroup grpRender;
    EDGUIPropRsrc render_Texture0;
    EDGUIPropRsrc render_Texture1;
    EDGUIPropRsrc render_Texture2;
    EDGUIPropRsrc render_Texture3;
    EDGUIPropRsrc render_Shader;
    EDGUIPropBool render_Additive;
    EDGUIPropBool render_Stretch;
    
    ParticleSystem* m_psys;
    ParticleSystem::Emitter* m_emitter;
};
 
struct EdParticleSystem : EDGUILayoutRow
{
    EdParticleSystem() :
        m_ctlGroup( true, "Particle system properties" ),
        m_ctlGravity( V3(0,0,-10), 2, V3(-8192), V3(8192) ),
        m_ctlMaxGroupCount( 10, 1, 128 ),
        m_ctlGlobalScale( 1, 2, 0.001f, 1000.0f ),
        m_ctlLooping( true ),
        m_ctlRetriggerTime( V2(1,0.1f), 2, V2(0), V2(1000) )
    {
        tyname = "particlesystem";
        
        m_ctlGroup.caption = "Particle system properties";
        m_ctlGravity.caption = "Gravity";
        m_ctlMaxGroupCount.caption = "# lighting groups";
        m_ctlGlobalScale.caption = "Global scale";
        m_ctlLooping.caption = "Looping";
        m_ctlRetriggerTime.caption = "Retrigger time/ext.";
        m_ctlAddEmitter.caption = "- Add emitter -";
        
        m_ctlGroup.SetOpen( true );
        Add( &m_ctlGroup );
        
        Reset();
    }
    
    int OnEvent( EDGUIEvent* e )
    {
        switch( e->type )
        {
        case EDGUI_EVENT_BTNCLICK:
            for( size_t i = 0; i < m_ctlEmEditBtns.size(); ++i )
            {
                if( e->target == &m_ctlEmEditBtns[ i ] )
                {
                    EDGUILayoutRow::OnEvent( e );
                    EditEmitter( i );
                    return 1;
                }
            }
            if( e->target == &m_ctlAddEmitter )
            {
                EDGUILayoutRow::OnEvent( e );
                AddDefaultEmitter();
                m_psys.OnRenderUpdate();
                EditEmitter( m_psys.emitters.size() - 1 );
                return 1;
            }
        case EDGUI_EVENT_PROPEDIT:
            if( e->target == &m_ctlGravity ) m_psys.gravity = m_ctlGravity.m_value;
            if( e->target == &m_ctlMaxGroupCount ) m_psys.maxGroupCount = m_ctlMaxGroupCount.m_value;
            if( e->target == &m_ctlGlobalScale ) m_psys.globalScale = m_ctlGlobalScale.m_value;
            if( e->target == &m_ctlLooping ) m_psys.looping = m_ctlLooping.m_value;
            if( e->target == &m_ctlRetriggerTime ) m_psys.retriggerTimeExt = m_ctlRetriggerTime.m_value;
            return 1;
        }
        return EDGUILayoutRow::OnEvent( e );
    }
    
    void Tick( float dt )
    {
        m_psys.Tick( dt );
        m_psys.PreRender();
    }
    
    void Data2UI()
    {
        m_ctlGroup.Clear();
        
        m_ctlGravity.SetValue( m_psys.gravity );
        m_ctlMaxGroupCount.SetValue( m_psys.maxGroupCount );
        m_ctlGlobalScale.SetValue( m_psys.globalScale );
        m_ctlLooping.SetValue( m_psys.looping );
        m_ctlRetriggerTime.SetValue( m_psys.retriggerTimeExt );
        
        m_ctlGroup.Add( &m_ctlGravity );
        m_ctlGroup.Add( &m_ctlMaxGroupCount );
        m_ctlGroup.Add( &m_ctlGlobalScale );
        m_ctlGroup.Add( &m_ctlLooping );
        m_ctlGroup.Add( &m_ctlRetriggerTime );
        
        m_ctlEmEditBtns.clear();
        m_ctlEmEditBtns.reserve( m_psys.emitters.size() );
        for( size_t i = 0; i < m_psys.emitters.size(); ++i )
        {
            m_ctlEmEditBtns.push_back( EDGUIButton() );
            EDGUIButton& B = m_ctlEmEditBtns.last();
            
            char bfr[32];
            sprintf( bfr, "Emitter #%d", (int)i + 1 );
            B.caption = bfr;
            B.id1 = i;
            
            m_ctlGroup.Add( &B );
        }
        
        m_ctlGroup.Add( &m_ctlAddEmitter );
    }
    
    void Reset()
    {
        m_psys = ParticleSystem();
        AddDefaultEmitter();
        
        m_psys.AddToScene( g_EdScene );
        m_psys.OnRenderUpdate();
        m_psys.SetTransform( Mat4::Identity );
        m_psys.Play();
    }
    void AddDefaultEmitter()
    {
        if( g_UIGraphEd ) // order of initialization
            g_UIGraphEd->UnsetCurve();
        m_psys.emitters.push_back( ParticleSystem::Emitter() );
        ParticleSystem::Emitter& E = m_psys.emitters.last();
        E.curve_ColorVal.valueRange = V2(1);
        E.curve_Opacity.valueRange = V2(0,1);
        Vec2 opa[] = { V2(0,0), V2(1,0), V2(1,0), V2(0,0) };
        E.curve_Opacity.values.append( opa, sizeof(opa)/sizeof(opa[0]) );
        E.curve_Size.valueRange = V2(1);
        E.create_VelMacroDvg = 0.5f;
        E.render_Textures[0] = GR_GetTexture( "textures/particles/spark_fast.png" );
    }
    
    EDGUIItem* GetSystem()
    {
        Data2UI();
        return this;
    }
    EDGUIItem* GetEmitter( size_t which )
    {
        m_ctlEmitter.Prepare( &m_psys, which );
        return &m_ctlEmitter;
    }
    
    void EditEmitter( size_t which );
    
    void Play(){ m_psys.Play(); }
    void Stop(){ m_psys.Stop(); }
    void Trigger(){ m_psys.Trigger(); }
    
    template< class T > void Serialize( T& arch )
    {
        m_psys.Serialize( arch, false );
    }
    
    ParticleSystem m_psys;
    
    EdEmitter m_ctlEmitter;
    
    EDGUIGroup m_ctlGroup;
    EDGUIPropVec3 m_ctlGravity;
    EDGUIPropInt m_ctlMaxGroupCount;
    EDGUIPropFloat m_ctlGlobalScale;
    EDGUIPropBool m_ctlLooping;
    EDGUIPropVec2 m_ctlRetriggerTime;
    Array< EDGUIButton > m_ctlEmEditBtns;
    EDGUIButton m_ctlAddEmitter;
};
 
 
 
struct EDGUIMainFrame : EDGUIFrame, EDGUIRenderView::FrameInterface
{
    EDGUIMainFrame() :
        m_UIMenuSplit( true, 26, 0 ),
        m_UIGraphSplit( true, 0, 0.7f ),
        m_UIParamSplit( false, 0, 0.7f ),
        m_UIRenderView( g_EdScene, this )
    {
        tyname = "mainframe";
        
        g_UIGraphEd = &m_UIGraph;
        
        Add( &m_UIMenuSplit );
        m_UIMenuSplit.SetFirstPane( &m_UIMenuButtons );
        m_UIMenuSplit.SetSecondPane( &m_UIParamSplit );
        m_UIParamSplit.SetFirstPane( &m_UIGraphSplit );
        m_UIParamSplit.SetSecondPane( &m_UIParamScroll );
        m_UIGraphSplit.SetFirstPane( &m_UIRenderView );
        m_UIGraphSplit.SetSecondPane( &m_UIGraph );
        m_UIParamScroll.Add( &m_UIParamList );
        
        // menu
        m_MB_Cat0.caption = "File:";
        m_MBNew.caption = "New";
        m_MBOpen.caption = "Open";
        m_MBSave.caption = "Save";
        m_MBSaveAs.caption = "Save As";
        m_MB_Cat1.caption = "Playback:";
        m_MBPlay.caption = "Play";
        m_MBStop.caption = "Stop";
        m_MBTrigger.caption = "Trigger";
        m_MB_Cat2.caption = "Edit:";
        m_MBEditSystem.caption = "System";
        m_UIMenuButtons.Add( &m_MB_Cat0 );
        m_UIMenuButtons.Add( &m_MBNew );
        m_UIMenuButtons.Add( &m_MBOpen );
        m_UIMenuButtons.Add( &m_MBSave );
        m_UIMenuButtons.Add( &m_MBSaveAs );
        m_UIMenuButtons.Add( &m_MB_Cat1 );
        m_UIMenuButtons.Add( &m_MBPlay );
        m_UIMenuButtons.Add( &m_MBStop );
        m_UIMenuButtons.Add( &m_MBTrigger );
        m_UIMenuButtons.Add( &m_MB_Cat2 );
        m_UIMenuButtons.Add( &m_MBEditSystem );
    }
    
    int OnEvent( EDGUIEvent* e )
    {
        switch( e->type )
        {
        case EDGUI_EVENT_BTNCLICK:
            if(0);
            
            else if( e->target == &m_MBNew ) PS_New();
            else if( e->target == &m_MBOpen ) PS_Open();
            else if( e->target == &m_MBSave ) PS_Save();
            else if( e->target == &m_MBSaveAs ) PS_SaveAs();
            
            else if( e->target == &m_MBPlay ) g_EdPS->Play();
            else if( e->target == &m_MBStop ) g_EdPS->Stop();
            else if( e->target == &m_MBTrigger ) g_EdPS->Trigger();
            
            else if( e->target == &m_MBEditSystem )
            {
                ClearParamList();
                AddToParamList( g_EdPS->GetSystem() );
            }
            
            return 1;
            
        case EDGUI_EVENT_PROPCHANGE:
            if( e->target == g_UIPSOpenPicker )
            {
                PS_Real_Open( g_UIPSOpenPicker->GetValue() );
            }
            if( e->target == g_UIPSSavePicker )
            {
                PS_Real_Save( g_UIPSSavePicker->GetValue() );
            }
            return 1;
        }
        return EDGUIFrame::OnEvent( e );
    }
    
    bool ViewEvent( EDGUIEvent* e )
    {
        return true;
    }
    
    void DebugDraw()
    {
    }
    
    void AddToParamList( EDGUIItem* item )
    {
        m_UIParamList.Add( item );
    }
    void ClearParamList()
    {
        while( m_UIParamList.m_subitems.size() )
            m_UIParamList.Remove( m_UIParamList.m_subitems.last() );
    }
    
    void ResetEditorState()
    {
        ClearParamList();
    }
    void PS_New()
    {
        m_fileName = "";
        g_EdPS->Reset();
        ResetEditorState();
    }
    void PS_Open()
    {
        g_UIPSOpenPicker->Reload();
        g_UIPSOpenPicker->Open( this, "" );
        m_frame->Add( g_UIPSOpenPicker );
    }
    void PS_Save()
    {
        if( m_fileName.size() )
        {
            PS_Real_Save( m_fileName );
        }
        else
        {
            PS_SaveAs();
        }
    }
    void PS_SaveAs()
    {
        g_UIPSSavePicker->Reload();
        g_UIPSSavePicker->Open( this, "" );
        m_frame->Add( g_UIPSSavePicker );
    }
    void PS_Real_Open( const String& str )
    {
        LOG << "Trying to open particle system: " << str;
        
        char bfr[ 256 ];
        sgrx_snprintf( bfr, sizeof(bfr), "psys/%.*s.psy", TMIN( (int) str.size(), 200 ), str.data() );
        ByteArray data;
        if( !FS_LoadBinaryFile( bfr, data ) )
        {
            LOG_ERROR << "FAILED TO LOAD PSYS FILE: " << bfr;
            return;
        }
        
        ResetEditorState();
        
        ByteReader br( &data );
        g_EdPS->Serialize( br );
        if( br.error )
        {
            LOG_ERROR << "FAILED TO READ PSYS FILE (at " << (int) br.pos << "): " << bfr;
            return;
        }
        
        m_fileName = str;
        g_EdPS->Play();
    }
    void PS_Real_Save( const String& str )
    {
        LOG << "Trying to save particle system: " << str;
        ByteArray data;
        ByteWriter bw( &data );
        
        bw << *g_EdPS;
        
        char bfr[ 256 ];
        sgrx_snprintf( bfr, sizeof(bfr), "psys/%.*s.psy", TMIN( (int) str.size(), 200 ), str.data() );
        if( !FS_SaveBinaryFile( bfr, data.data(), data.size() ) )
        {
            LOG_ERROR << "FAILED TO SAVE PSYS FILE: " << bfr;
            return;
        }
        
        m_fileName = str;
    }
    
    String m_fileName;
    
    // core layout
    EDGUILayoutSplitPane m_UIMenuSplit;
    EDGUILayoutSplitPane m_UIGraphSplit;
    EDGUILayoutSplitPane m_UIParamSplit;
    EDGUILayoutColumn m_UIMenuButtons;
    EDGUIVScroll m_UIParamScroll;
    EDGUILayoutRow m_UIParamList;
    EDGUIRenderView m_UIRenderView;
    EdDualGraph m_UIGraph;
 
    // menu
    EDGUILabel m_MB_Cat0;
    EDGUIButton m_MBNew;
    EDGUIButton m_MBOpen;
    EDGUIButton m_MBSave;
    EDGUIButton m_MBSaveAs;
    EDGUILabel m_MB_Cat1;
    EDGUIButton m_MBPlay;
    EDGUIButton m_MBStop;
    EDGUIButton m_MBTrigger;
    EDGUILabel m_MB_Cat2;
    EDGUIButton m_MBEditSystem;
};
 
void EdParticleSystem::EditEmitter( size_t which )
{
    g_UIFrame->ClearParamList();
    g_UIFrame->AddToParamList( GetEmitter( which ) );
}
 
 
 
 
struct PSEditor : IGame
{
    bool OnInitialize()
    {
        GR2D_LoadFont( "core", "fonts/lato-regular.ttf" );
        GR2D_SetFont( "core", 12 );
        
        g_UITexPicker = new EDGUISDTexPicker( "textures/particles" );
        g_UIShaderPicker = new EDGUIShaderPicker;
        g_UIMeshPicker = new EDGUIMeshPicker;
        g_UIPSOpenPicker = new EDGUIPSOpenPicker;
        g_UIPSSavePicker = new EDGUIPSSavePicker;
        g_UIPSISFXPicker = new EDGUIPSISFXPicker;
        
        // core layout
        g_EdScene = GR_CreateScene();
        g_EdScene->camera.position = V3(3);
        g_EdScene->camera.UpdateMatrices();
        g_EdPS = new EdParticleSystem;
        g_UIFrame = new EDGUIMainFrame();
        g_UIFrame->Resize( GR_GetWidth(), GR_GetHeight() );
        
        // param area
        g_UIFrame->AddToParamList( g_EdPS->GetSystem() );
        
        return true;
    }
    void OnDestroy()
    {
        delete g_UIPSISFXPicker;
        g_UIPSISFXPicker = NULL;
        delete g_UIPSSavePicker;
        g_UIPSSavePicker = NULL;
        delete g_UIPSOpenPicker;
        g_UIPSOpenPicker = NULL;
        delete g_UIMeshPicker;
        g_UIMeshPicker = NULL;
        delete g_UIShaderPicker;
        g_UIShaderPicker = NULL;
        delete g_UITexPicker;
        g_UITexPicker = NULL;
        delete g_UIFrame;
        g_UIFrame = NULL;
        delete g_EdPS;
        g_EdPS = NULL;
        g_EdScene = NULL;
    }
    void OnEvent( const Event& e )
    {
        g_UIFrame->EngineEvent( &e );
    }
    void OnTick( float dt, uint32_t gametime )
    {
        GR2D_SetViewMatrix( Mat4::CreateUI( 0, 0, GR_GetWidth(), GR_GetHeight() ) );
        g_UIFrame->m_UIRenderView.UpdateCamera( dt );
        g_EdPS->Tick( dt );
        g_UIFrame->Draw();
    }
}
g_Game;
 
 
extern "C" EXPORT IGame* CreateGame()
{
    return &g_Game;
}

Comments