Welcome, Guest. Please login or register. Did you miss your activation email?

Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.


Topics - Alf P. Steinbach

Pages: [1]
1
General discussions / Simplifying SFML
« on: March 06, 2014, 06:39:35 am »
I was looking for a portable C++ framework for some simple simulation of planets/bodies dancing around in space, which was meant for some writings. I tried SDL but it seemed too low level. SFML however seems all right so far (I just worked through some of the tutorials).

What I want to expose of code is just the logical model, with a minimum of support code.

So I factored out the support code so that a main program (working code), with antialised drawing and support for window resizing without stretching, looks like this, where xsf is my namespace for this:

#include <xsf/all.h>        // xfs::*, fs::*

class Moving_ball
    : public xsf::Model
{
using Circle = xsf::shape::Circle;
private:
    Circle          ball_{ Circle::Params()
        .radius( 10.0 )
        .fillcolor( sf::Color::Green )
        };
    xsf::Size<>     velocity_{ 30*3.14159f, 30*2.71828f };

    void on_update( sf::Time const time_now, sf::Time const dt ) override
    {
        XSF_INTENTIONALLY_UNUSED( time_now );

        float const seconds = dt.asSeconds();
        ball_.move( seconds*velocity_ );

        sf::Vector2f const position = ball_.getPosition();
        if( position.x < 0 && velocity_.x < 0 )     { velocity_.x *= -1; }
        if( position.x > 100 && velocity_.x > 0 )   { velocity_.x *= -1; }
        if( position.y < 0 && velocity_.y < 0 )     { velocity_.y *= -1; }
        if( position.y > 100 && velocity_.y > 0 )   { velocity_.y *= -1; }
    }

public:
    Moving_ball()
    { drawables_.insert( &ball_ ); }
};

auto main()
    -> int
{
    Moving_ball model;
    xsf::Window window( "SFML works, yay!", &model );
    xsf::Controller( &model ).process_events();
}
 

But maybe others have done things in the same direction before, and there is already some C++ support framework for SFML that reduces code to something like the above?

Cheers,

- Alf

PS: This thing is still only at the stage of exploring the basics of SFML, and wrapping it in C++. I just think it's a good idea to get feedback before one has done too much! Both about directions, technicalities and alternatives.

PPS: The support code, complete as-of-this-posting (I have not yet even split it up in separate headers):

#pragma once
// Copyright © 2014 Alf P. Steinbach.

#include <SFML/Graphics.hpp>

#include <algorithm>    // std::count_if
#include <functional>   // std::function
#include <set>          // std::set

#define XSF_INTENTIONALLY_UNUSED( arg )   (void) arg; struct arg;

namespace xsf {

    //---------------------------------------- Geometry:

    template< class Number = float >
    class Point
        : public sf::Vector2< Number >
    {
    using Base = sf::Vector2< Number >;
    public:
        Point(): Base( 0, 0 ) {}
        Point( Number const x, Number const y ): Base( x, y ) {}
    };

    template< class Number = float >
    class Size
        : public sf::Vector2< Number >
    {
    using Base = sf::Vector2< Number >;
    public:
        Size(): Base( 0, 0 ) {}
        Size( Number const x, Number const y ): Base( x, y ) {}
    };

    inline
    auto antialiased()
        -> sf::ContextSettings
    {
        sf::ContextSettings settings;

        settings.antialiasingLevel = 8;
        return settings;
    }


    //---------------------------------------- Time:

    inline
    auto abs( sf::Time const& time )
        -> sf::Time
    { return (time < sf::Time()? -time : time); }

    inline
    auto sign( sf::Time const& time )
        -> int
    { return (time < sf::Time()? -1 : time == sf::Time()? 0 : +1); }


    //---------------------------------------- Model:

    class Model
    {
    friend class Controller;
    private:
        sf::Time    max_dt_;

        // dt, for "delta of time", is the time span since the previous update.
        virtual
        void on_update( sf::Time const time_now, sf::Time const dt )
            = 0;

    protected:
        std::set< sf::Drawable const* > drawables_;

        void update_to( sf::Time const time_now, sf::Time const dt )
        {
            sf::Time const abs_dt   = abs( dt );
            if( abs_dt <= max_dt_ )
            {
                on_update( time_now, dt );
            }
            else
            {
                bool const      is_positive     = (dt > sf::Time());
                sf::Time const  previous_time   = time_now - dt;

                sf::Time elapsed;
                for( elapsed = max_dt_;  elapsed < abs_dt;  elapsed += max_dt_ )
                {
                    if( is_positive )
                    {
                        on_update( previous_time + elapsed, max_dt_ );
                    }
                    else
                    {
                        on_update( previous_time - elapsed, -max_dt_ );
                    }
                }
                elapsed -= max_dt_;
                if( elapsed < abs_dt )
                {
                    sf::Time const remainder = abs_dt - elapsed;
                    on_update( time_now, (is_positive? remainder : -remainder) );
                }
            }
        }

        void set_max_dt( sf::Time const& new_max_dt )
        { max_dt_ = new_max_dt; }

    public:
        auto max_dt() const -> sf::Time { return max_dt_; }

        auto drawables() const
            -> std::set< sf::Drawable const* > const&
        { return drawables_; }

        virtual ~Model() {}

        Model()
            : max_dt_( sf::seconds( 0.1f ) )
        {}
    };

    namespace shape {

        class Circle
            : public sf::CircleShape
        {
        using Base = sf::CircleShape;
        public:
            class Params
            {
            friend class Circle;
            private:
                sf::Color       fillcolor_      { sf::Color::White };
                int             n_points_       { 90 };
                Point<float>    position_       { 0, 0 };
                float           radius_         { 100 };

            public:
                using Outer_class = Circle;

                auto fillcolor( sf::Color const& value )
                    -> Params&
                { fillcolor_ = value; return *this; }

                auto n_points( int const value )
                    -> Params&
                { n_points_ = value; return *this; }

                auto position( Point<float> const& value )
                    -> Params&
                { position_ = value; return *this; }

                auto radius( float const value )
                    -> Params&
                { radius_ = value; return *this; }
            };

            void move_to( Point<float> const& position )
            { Base::setPosition( position ); }

            Circle( float const radius, int const n_points = 90 )
                : Base( radius, n_points )
            {}

            Circle( Params const& params )
                : Base( params.radius_, params.n_points_ )
            {
                Base::setFillColor( params.fillcolor_ );
                Base::setPosition( params.position_ );
            }
        };

    }  // namespace shape


    //---------------------------------------- Rendering:

    class Controller;

    class Window
        : public sf::RenderWindow
    {
    friend class Controller;
    private:
        Model const*    p_model_;

        // Called by controller's event processing loop.
        static auto current_windows()
            -> std::set<Window*>&
        {
            static std::set<Window*> the_windows;
            return the_windows;
        }

        // Called by controller's event processing loop.
        virtual void on( sf::Event const event )
        {
            switch( event.type )
            {
                case sf::Event::Closed:
                {
                    close();
                    break;
                }
                case sf::Event::Resized:
                {
                    // Avoid stretching of graphics to window size.
                    sf::FloatRect const visible_area(
                        0, 0, (float)event.size.width, (float)event.size.height
                        );
                    setView( sf::View( visible_area ) );
                    break;
                }
            }
        }

        virtual void render_model()
        {
            if( p_model_ != nullptr )
            {
                for( auto p_drawable : p_model_->drawables() )
                {
                    draw( *p_drawable );
                }
            }
        }

        // Called by controller's event processing loop.
        virtual void render()
        {
            clear();
            render_model();
        }

    public:
        ~Window() override
        { current_windows().erase( this ); }

        explicit Window(
            char const* const   title,
            Model const* const  p_model = nullptr,
            Point<int> const&   size    = Point<int>( 320, 200 )
            )
            : sf::RenderWindow(
                sf::VideoMode( size.x, size.y ),
                title,
                sf::Style::Default,
                antialiased()
                )
            , p_model_( p_model )
        {
            sf::Window::setVerticalSyncEnabled( true );
            current_windows().insert( this );
        }
    };


    //---------------------------------------- Control:

    class Controller
    {
    public:
        using Event_processing_func = std::function< void( Window&, sf::Event const& ) >;

        static
        void default_event_processing( Window&  window, sf::Event const& event )
        { window.on( event ); }

        using Rendering_func = std::function< void( Window& ) >;

        static
        void default_rendering( Window& window )
        { window.render(); }

    private:
        sf::Clock   clock_;
        sf::Time    previous_time_;
        Model*      p_model_;

        void update()
        {
            sf::Time const current_time  = clock_.getElapsedTime();
            if( p_model_ != nullptr )
            {
                p_model_->update_to( current_time, current_time - previous_time_ );
            }
            previous_time_ = current_time;
        }

        void dispatch_events( Event_processing_func const process_event )
        {
            sf::Event event;
            for( auto const p_window : Window::current_windows() )
            {
                while( p_window->pollEvent( event ) )
                {
                    process_event( *p_window, event );
                }
            }
        }

        auto render_and_count_open_windows( Rendering_func const render )
           -> int
         {
            int n = 0;
            for( auto const p_window : Window::current_windows() )
            {
                if( p_window->isOpen() )
                {
                    render( *p_window );
                    p_window->display();
                    ++n;
                }
            }
            return n;
        }

    public:
        void process_events(
            Rendering_func const        render          = default_rendering,
            Event_processing_func const process_event   = default_event_processing
            )
        {
            for( ;; )
            {
                update();
                dispatch_events( process_event );

                int const n_open_windows = render_and_count_open_windows( render );
                if( n_open_windows == 0 ) { break; }
            }
        }

        Controller( Model* p_model = nullptr )
            : p_model_( p_model )
        {}
    };

}  // namespace xsf
 

Pages: [1]
anything