// TODO mutex guard instead of lock/unlock #ifndef SDLPP_HPP #define SDLPP_HPP #include #include #include #include #include #include #include #include #include #include #include #define SDLPP_TEXT_LEFT 0x0001 #define SDLPP_TEXT_RIGHT 0x0002 #define SDLPP_TEXT_CENTER 0x0004 #define SDLPP_TEXT_TOP 0x0008 #define SDLPP_TEXT_BOTTOM 0x0010 namespace SDLPP { int hex2num( char c ); std::tuple< int, int, int, int > getColorsHEX( const std::string &color ); SDL_Color getSDLColorHEX( const std::string &color ); std::tuple< int, int, int, int > getColorsSDLColor( const SDL_Color &color ); SDL_Color getSDLColorTuple( const std::tuple< int, int, int, int > &tuple ); class Window { public: Window() : Window( "SDL Window", 640, 480, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED ) {} Window( const std::string &window_name ) : Window( window_name, 640, 480, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED ) {} Window( const std::string &window_name, uint32_t width, uint32_t height ) : Window( window_name, width, height, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED ) {} Window( const std::string &window_name, uint32_t width, uint32_t height, uint32_t posx, uint32_t posy ) { window = SDL_CreateWindow( window_name.c_str(), posx, posy, width, height, SDL_WINDOW_SHOWN ); if ( window == NULL ) { std::cerr << "SDL could not create a window! SDL_Error: " << SDL_GetError() << std::endl; throw "Couldn't create window"; } } void setResizable(bool resizable) { SDL_SetWindowResizable(window, resizable ? SDL_TRUE : SDL_FALSE); } ~Window() { SDL_DestroyWindow( window ); } SDL_Window *getWindowPtr() { return window; } private: SDL_Window *window = NULL; }; class Renderer { public: Renderer() = delete; Renderer( Window &window ) { renderer = SDL_CreateRenderer( window.getWindowPtr(), -1, SDL_RENDERER_ACCELERATED ); if ( renderer == NULL ) { std::cerr << "SDL could not create a renderer! SDL_Error: " << SDL_GetError(); throw "Couldn't create renderer"; } SDL_SetRenderDrawColor( renderer, 0xFF, 0xFF, 0xFF, 0xFF ); } ~Renderer() { SDL_DestroyRenderer( renderer ); } SDL_Renderer *getRendererPtr() { return renderer; } std::pair< int, int > getDimensions() const { int width = 0, height = 0; SDL_GetRendererOutputSize( renderer, &width, &height ); return { width, height }; } int getWidth() const { return getDimensions().first; } int getHeight() const { return getDimensions().second; } int getSmallerSide() const { auto dimensions = getDimensions(); return dimensions.first < dimensions.second ? dimensions.first : dimensions.second; } int getLargerSide() const { auto dimensions = getDimensions(); return dimensions.first > dimensions.second ? dimensions.first : dimensions.second; } void setBlendMode( SDL_BlendMode blendMode ) { SDL_SetRenderDrawBlendMode( renderer, blendMode ); } void setRenderColiders( bool render ) { render_coliders = render; } bool getRenderColiders() { return render_coliders; } private: SDL_Renderer *renderer = NULL; bool render_coliders = false; }; class Font { public: Font() = delete; Font( const std::string &font, int size ) { font_ptr = TTF_OpenFont( font.c_str(), size ); if ( font_ptr == NULL ) { std::cerr << "Unable to load font '" << font << "': TTF Error: " << TTF_GetError() << std::endl; throw "TTF_OpenFont error"; } } ~Font() { TTF_CloseFont( font_ptr ); } const TTF_Font *getFont() const { return font_ptr; } TTF_Font *getFont() { return font_ptr; } void setOutline( int size ) { TTF_SetFontOutline( font_ptr, size ); } int getOutline() { return TTF_GetFontOutline( font_ptr ); } void setStyle( int style ) { TTF_SetFontStyle( font_ptr, style ); } void setHinting( int hinting ) { TTF_SetFontHinting( font_ptr, hinting ); } private: TTF_Font *font_ptr; }; class Texture { public: Texture() = delete; Texture( std::shared_ptr< Renderer > &renderer, const std::string &img_path ) : Texture( renderer, img_path, "" ) {} Texture( std::shared_ptr< Renderer > &renderer, const std::string &img_path, const std::string &color_key ) { SDL_Surface *surface = IMG_Load( img_path.c_str() ); if ( surface == NULL ) { std::cerr << "Unable to load image '" << img_path << "': IMG Error: " << IMG_GetError() << std::endl; throw "IMG_Load error"; } if ( !color_key.empty() ) { auto colors = getColorsHEX( color_key ); SDL_SetColorKey( surface, SDL_TRUE, SDL_MapRGB( surface->format, std::get< 0 >( colors ), std::get< 1 >( colors ), std::get< 2 >( colors ) ) ); } setTextureFromSurface( renderer, surface ); } Texture( std::shared_ptr< Renderer > &renderer, Font &font, const std::string &text, const std::string &color = "FFFFFF", const std::string &outline_color = "000000", const int outline_size = -1 ) { if ( outline_size != -1 ) { font.setOutline( outline_size ); } int og_outline = 0; SDL_Surface *bg_surface = NULL; if ( ( og_outline = font.getOutline() ) != 0 ) { bg_surface = TTF_RenderUTF8_Blended( font.getFont(), text.c_str(), getSDLColorHEX( outline_color ) ); if ( bg_surface == NULL ) { std::cerr << "Unable to render text '" << text << "': TTF Error: " << TTF_GetError() << std::endl; throw "TTF_RenderUTF8_Shaded error"; } font.setOutline( 0 ); } SDL_Surface *surface = TTF_RenderUTF8_Blended( font.getFont(), text.c_str(), getSDLColorHEX( color ) ); if ( surface == NULL ) { std::cerr << "Unable to render text '" << text << "': TTF Error: " << TTF_GetError() << std::endl; throw "TTF_RenderUTF8_Shaded error"; } if ( og_outline != 0 ) { SDL_Rect rect = { og_outline, og_outline, surface->w, surface->h }; SDL_SetSurfaceBlendMode( surface, SDL_BLENDMODE_BLEND ); SDL_BlitSurface( surface, NULL, bg_surface, &rect ); SDL_FreeSurface( surface ); surface = bg_surface; bg_surface = NULL; font.setOutline( og_outline ); } setTextureFromSurface( renderer, surface ); } ~Texture() { SDL_DestroyTexture( texture ); } SDL_Texture *getTexturePtr() { return texture; } private: void setTextureFromSurface( std::shared_ptr< Renderer > &renderer, SDL_Surface *surface ) { texture = SDL_CreateTextureFromSurface( renderer->getRendererPtr(), surface ); if ( texture == NULL ) { std::cerr << "Unable to create texture from surface! SDL Error: " << SDL_GetError() << std::endl; throw "Texture error"; } SDL_FreeSurface( surface ); } SDL_Texture *texture = NULL; }; class CollisionPolygon { public: CollisionPolygon( double x, double y ) { original_x = x; original_y = y; position_x = 0; position_y = 0; } virtual ~CollisionPolygon() {} virtual bool colidesWith( const CollisionPolygon &other ) const = 0; virtual bool isCircle() const = 0; virtual bool isInfinite() const { return infinite; } virtual void setInfinite() { infinite = true; } virtual int topmost() const = 0; virtual int bottommost() const = 0; virtual int leftmost() const = 0; virtual int rightmost() const = 0; virtual void updateCollision( int x, int y, int w, int h ) { position_x = original_x * w + x; position_y = original_y * h + y; } virtual void render( Renderer &renderer, const std::tuple< int, int, int, int > &color ) = 0; virtual void render( Renderer &renderer ) = 0; int getX() const { return position_x; } int getY() const { return position_y; } void setColor( const std::string &color ) { sdl_color = getSDLColorHEX( color ); } void setOutlineColor( const std::string &color ) { sdl_outline = getSDLColorHEX( color ); } protected: double original_x; double original_y; int position_x; int position_y; bool infinite = false; SDL_Color sdl_color = { 0, 0, 0, 0 }; SDL_Color sdl_outline = { 0, 0, 0, 0 }; }; class Scene; class RenderObject { public: RenderObject( const std::shared_ptr< Renderer > &r ) : renderer( r ) {} virtual ~RenderObject() {} virtual void render() = 0; virtual int leftmost() = 0; virtual int topmost() = 0; virtual int rightmost() = 0; virtual int bottommost() = 0; virtual int collisionPushX() = 0; virtual int collisionPushY() = 0; virtual int collisionWidth() = 0; virtual int collisionHeight() = 0; virtual void specialAction( int code ) = 0; virtual std::pair< std::pair< double, double >, std::pair< double, double > > getDoubleRect() const = 0; virtual void setPos( double x, double y ) = 0; virtual void setPos(const std::pair &pos) = 0; virtual std::pair< double, double > getPos() const = 0; bool colidesWith( const RenderObject &other ) const { if ( !hasCollisions() || !other.hasCollisions() || getHidden() || other.getHidden() ) { return false; } for ( const auto &x : collisions ) { for ( const auto &y : other.getCollisions() ) { if ( x->colidesWith( *y ) ) return true; } } return false; } template < class T > void addCollision( const T &p ) { collisions.push_back( std::make_shared< T >( p ) ); collisions.back()->updateCollision( collisionPushX(), collisionPushY(), collisionWidth(), collisionHeight() ); } bool hasCollisions() const { return !collisions.empty(); } const std::vector< std::shared_ptr< CollisionPolygon > > & getCollisions() const { return collisions; } virtual void setTexture( const std::shared_ptr< Texture > &t ) { texture = t; } virtual void setTexture( const std::string &img_path ) { texture = std::make_shared< Texture >( renderer, img_path ); } virtual void setTexture( Font &font, const std::string &text, const std::string &color = "FFFFFF", const std::string &outline_color = "000000", int outline_size = -1 ) { texture = std::make_shared< Texture >( renderer, font, text, color, outline_color, outline_size ); } virtual void setColor( const std::string &color ) = 0; virtual void setOutlineColor( const std::string &color ) = 0; virtual void unsetTexture() { texture.reset(); } virtual void unsetColor() { polygon.reset(); } // per second, relative to window width void setMovementSpeed( double speed ) { movementSpeed = speed; } void addMovement( int x, int y ) { movementDirection.first += x; movementDirection.second += y; } void resetMovementX() { movementDirection.first = 0; } void resetMovementY() { movementDirection.second = 0; } void clearColided() { colidedWith.clear(); } void addColided( std::shared_ptr< RenderObject > &obj ) { colidedWith.push_back( obj ); } std::vector< std::shared_ptr< RenderObject > > &getColidedWith() { return colidedWith; } void setId( uint64_t input_id ) { id = input_id; } uint64_t getId() { return id; } void setHidden( bool hid ) { hidden = hid; } bool getHidden() const { return hidden; } void destroy() { setHidden( true ); kill = true; } bool getKilled() { return kill; } void setColiderColor( const std::string &color ) { colider_color = getColorsHEX( color ); } virtual void move( int ticks ) = 0; virtual void custom_move( int ticks ) = 0; virtual void updateSizeAndPosition() = 0; virtual SDL_Rect getRect() = 0; void setPermanent( bool perm = true ) { permanent = perm; setStatic(perm); } bool getPermanent() const { return permanent; } virtual void centerX() = 0; virtual std::shared_ptr copySelf() = 0; bool isStatic() { return is_static; } void setStatic(bool stat = true) { is_static = stat; } std::shared_ptr getRenderer() const { return renderer; } protected: std::vector< std::shared_ptr< CollisionPolygon > > collisions; std::shared_ptr< Texture > texture; std::shared_ptr< Renderer > renderer; std::shared_ptr< CollisionPolygon > polygon; double movementSpeed = 0; std::pair< int, int > movementDirection = {0,0}; std::vector< std::shared_ptr< RenderObject > > colidedWith; uint64_t id = -1; bool hidden = false; bool kill = false; std::tuple< int, int, int, int > colider_color = { 0x00, 0xFF, 0xFF, 0xFF }; uint64_t scene_id = -1; bool permanent = false; bool is_static = true; private: void setSceneID( int id ) { scene_id = id; } friend Scene; }; class Scene { public: Scene( std::shared_ptr< Renderer > &r ) : renderer( r ) { SDL_SetRenderDrawColor( renderer->getRendererPtr(), 0xFF, 0xFF, 0xFF, 0xFF ); prev_ticks = SDL_GetTicks(); } void addObject( const std::shared_ptr< RenderObject > &obj ) { render_mutex.lock(); render_objects.push_back( obj ); obj->setSceneID( ++max_object_id ); if ( obj->hasCollisions() ) { collision_objects.push_back( obj ); } if ( render_objects.size() == 1 ) { leftmost_obj = obj; rightmost_obj = obj; } else { auto rect = obj->getDoubleRect(); auto leftmost_rect = leftmost_obj->getDoubleRect(); if ( rect.first.first < leftmost_rect.first.first ) leftmost_obj = obj; auto rightmost_rect = rightmost_obj->getDoubleRect(); if ( rect.first.first + rect.second.first > rightmost_rect.first.first + rightmost_rect.second.first ) rightmost_obj = obj; } render_mutex.unlock(); } void setZIndex( const std::shared_ptr< RenderObject > &obj, int index ) { std::lock_guard guard(render_mutex); int original_index = 0; for(long unsigned int i = 0; i < render_objects.size(); i++) { if(render_objects[i] == obj) { original_index = i; } } if(original_index == index) return; if(original_index > index) original_index++; render_objects.insert(render_objects.begin() + index, obj); render_objects.erase(render_objects.begin() + original_index); } void moveDownZ( const std::shared_ptr &obj ) { moveZ(obj, -1); } void moveUpZ( const std::shared_ptr &obj ) { moveZ(obj, 1); } void moveZ( const std::shared_ptr &obj, int addition ) { int original_index = 0; for(long unsigned int i = 0; i < render_objects.size(); i++) { if(render_objects[i] == obj) { original_index = i; } } std::iter_swap(render_objects.begin() + original_index, render_objects.begin() + original_index + addition); } //TODO addCollision std::shared_ptr< RenderObject > getObject( int index ) { return render_objects[index]; } std::vector< std::shared_ptr< RenderObject > > getObjects() { return render_objects; } std::vector< std::shared_ptr< RenderObject > > getObjects(const std::unordered_set< int > &objectIDs) { std::vector< std::shared_ptr< RenderObject > > ret{}; for ( const auto &x : render_objects ) { if ( objectIDs.find( x->getId() ) != objectIDs.end() ) { ret.push_back( x ); } } return ret; } void movement() { checkKilled(); render_mutex.lock(); int now_ticks = SDL_GetTicks(); for ( const auto &x : render_objects ) { x->move( now_ticks - prev_ticks ); } prev_ticks = now_ticks; render_mutex.unlock(); } std::vector< std::shared_ptr< RenderObject > > getCollisions( RenderObject &r ) { if ( r.getHidden() ) return {}; std::vector< std::shared_ptr< RenderObject > > ret{}; for ( const auto &x : collision_objects ) { if ( x->colidesWith( r ) ) { ret.push_back( x ); } } return ret; } std::vector< std::shared_ptr< RenderObject > > getCollisions( RenderObject &r, const std::unordered_set< int > &objectIDs ) { if ( r.getHidden() ) return {}; std::vector< std::shared_ptr< RenderObject > > ret{}; for ( const auto &x : collision_objects ) { if ( objectIDs.find( x->getId() ) != objectIDs.end() && x->colidesWith( r ) ) { ret.push_back( x ); } } return ret; } void renderScene( bool clear_scene = true ) { checkKilled(); render_mutex.lock(); if ( clear_scene ) SDL_RenderClear( renderer->getRendererPtr() ); if ( background && background->getTexturePtr() ) SDL_RenderCopy( renderer->getRendererPtr(), background->getTexturePtr(), NULL, NULL ); for ( const auto &x : render_objects ) { x->render(); } render_mutex.unlock(); } void presentScene() { SDL_RenderPresent( renderer->getRendererPtr() ); } void setBackground( std::shared_ptr< Texture > bg ) { background = bg; } void setBackground( const std::string &img_path ) { background = std::make_shared< Texture >( renderer, img_path ); } void updateSizeAndPosition() { checkKilled(); render_mutex.lock(); for ( auto &x : render_objects ) { x->updateSizeAndPosition(); for ( auto &col : x->getCollisions() ) { col->updateCollision( x->collisionPushX(), x->collisionPushY(), x->collisionWidth(), x->collisionHeight() ); } } render_mutex.unlock(); } void moveEverything( double x, double y ) { checkKilled(); render_mutex.lock(); for ( auto &obj : render_objects ) { if ( obj->getPermanent() ) continue; auto curPos = obj->getDoubleRect(); obj->setPos( curPos.first.first + x, curPos.first.second + y ); } render_mutex.unlock(); } const std::shared_ptr< RenderObject > &leftmost() { return leftmost_obj; } const std::shared_ptr< RenderObject > &rightmost() { return rightmost_obj; } std::pair< int, int > getDimensions() const { return renderer->getDimensions(); } int getWidth() const { return renderer->getWidth(); } int getHeight() const { return renderer->getHeight(); } Renderer &getRenderer() { return *renderer; } std::shared_ptr getRendererShared() { return renderer; } void setPrevTicks( int ticks ) { prev_ticks = ticks; } void saveScene() { saved_render_objects.clear(); saved_collision_objects.clear(); for(auto &obj : render_objects) { if(!obj->isStatic()) saved_render_objects.push_back(obj->copySelf()); else saved_render_objects.push_back(obj); } for(auto &obj : collision_objects) { if(!obj->isStatic()) saved_collision_objects.push_back(obj->copySelf()); else saved_collision_objects.push_back(obj); } } void resetScene() { render_objects.clear(); collision_objects.clear(); for(auto &obj : saved_render_objects) { if(!obj->isStatic()) render_objects.push_back(obj->copySelf()); else render_objects.push_back(obj); } for(auto &obj : saved_collision_objects) { if(!obj->isStatic()) collision_objects.push_back(obj->copySelf()); else collision_objects.push_back(obj); } } private: void checkKilled() { render_mutex.lock(); std::vector< int > killed; std::vector< int > killed_collisions; for ( long unsigned int i = 0; i < render_objects.size(); i++ ) { if ( render_objects[i]->getKilled() ) killed.push_back( i ); if ( i < collision_objects.size() && collision_objects[i]->getKilled() ) killed_collisions.push_back( i ); } // reverse so we don't screw up indexing while going thorugh the kill indices std::reverse( killed.begin(), killed.end() ); std::reverse( killed_collisions.begin(), killed_collisions.end() ); for ( auto &index : killed ) { render_objects.erase( render_objects.begin() + index ); } for ( auto &index : killed_collisions ) { collision_objects.erase( collision_objects.begin() + index ); } render_mutex.unlock(); } std::vector< std::shared_ptr< RenderObject > > render_objects; std::vector< std::shared_ptr< RenderObject > > collision_objects; std::vector< std::shared_ptr< RenderObject > > saved_render_objects; std::vector< std::shared_ptr< RenderObject > > saved_collision_objects; std::shared_ptr< Renderer > renderer; std::shared_ptr< Texture > background; int prev_ticks = 0; std::shared_ptr< RenderObject > leftmost_obj; std::shared_ptr< RenderObject > rightmost_obj; uint64_t max_object_id = 0; std::mutex render_mutex; }; class Rect : public CollisionPolygon { public: Rect( double x, double y, double w, double h ) : CollisionPolygon( x, y ) { w_ = w; h_ = h; } virtual ~Rect() {} virtual bool colidesWith( const CollisionPolygon &other ) const override; virtual bool isCircle() const override { return false; } virtual int topmost() const override { return ( !isInfinite() || original_y != -1 ) * getY() + isInfinite() * -1; } virtual int bottommost() const override { return ( !isInfinite() || h_ != -1 ) * ( getY() + pixel_h ) + isInfinite() * -1; }; virtual int leftmost() const override { return ( !isInfinite() || original_x != -1 ) * getX() + isInfinite() * -1; } virtual int rightmost() const override { return ( !isInfinite() || w_ != -1 ) * ( getX() + pixel_w ) + isInfinite() * -1; } virtual void updateCollision( int x, int y, int w, int h ) override { position_x = original_x * w + x; position_y = original_y * h + y; pixel_w = w_ * w; pixel_h = h_ * h; } virtual void render( Renderer &renderer, const std::tuple< int, int, int, int > &color ) override { auto rect = getRect(); // outline with desired color at 50% opacity SDL_SetRenderDrawColor( renderer.getRendererPtr(), std::get< 0 >( color ), std::get< 1 >( color ), std::get< 2 >( color ), 0x80 ); SDL_RenderDrawRect( renderer.getRendererPtr(), &rect ); // fill with desired color at 25% opacity SDL_SetRenderDrawColor( renderer.getRendererPtr(), std::get< 0 >( color ), std::get< 1 >( color ), std::get< 2 >( color ), 0x40 ); SDL_RenderFillRect( renderer.getRendererPtr(), &rect ); } virtual void render( Renderer &renderer ) override { auto rect = getRect(); SDL_SetRenderDrawColor( renderer.getRendererPtr(), sdl_color.r, sdl_color.g, sdl_color.b, sdl_color.a ); SDL_RenderFillRect( renderer.getRendererPtr(), &rect ); SDL_SetRenderDrawColor( renderer.getRendererPtr(), sdl_outline.r, sdl_outline.g, sdl_outline.b, sdl_outline.a ); SDL_RenderDrawRect( renderer.getRendererPtr(), &rect ); } private: SDL_Rect getRect() { if ( !isInfinite() ) return { leftmost(), topmost(), pixel_w, pixel_h }; SDL_Rect r = { 0, 0, 0, 0 }; if ( ( r.x = leftmost() ) == -1 ) r.x = 0; if ( ( r.y = topmost() ) == -1 ) r.y = 0; if ( rightmost() == -1 ) r.w = std::numeric_limits< int >::max(); else r.w = pixel_w; if ( bottommost() == -1 ) r.h = std::numeric_limits< int >::max(); else r.h = pixel_h; return r; } double w_; double h_; int pixel_w; int pixel_h; }; class Circle : public CollisionPolygon { public: Circle( double x, double y, double rad ) : CollisionPolygon( x, y ) { original_rad = rad; } virtual ~Circle() {} virtual bool colidesWith( const CollisionPolygon &other ) const override; virtual bool isCircle() const override { return true; } virtual int topmost() const override { return getY() - rad_; } virtual int bottommost() const override { return getY() + rad_; }; virtual int leftmost() const override { return getX() - rad_; } virtual int rightmost() const override { return getX() + rad_; } virtual void updateCollision( int x, int y, int w, int h ) override { position_x = original_x * w + x; position_y = original_y * h + y; rad_ = original_rad * w; } virtual void render( Renderer &renderer, const std::tuple< int, int, int, int > &color ) override { std::vector< int > rect = { leftmost(), topmost(), rightmost(), bottommost() }; auto center_x = getX(); auto center_y = getY(); auto radsq = rad_ * rad_; for ( int i = rect[0]; i <= rect[2]; i++ ) { auto xdiff = center_x - i; auto xdist = xdiff * xdiff; auto allowed_rad = sqrt( radsq - xdist ); SDL_SetRenderDrawColor( renderer.getRendererPtr(), std::get< 0 >( color ), std::get< 1 >( color ), std::get< 2 >( color ), 0x40 ); SDL_RenderDrawLine( renderer.getRendererPtr(), i, center_y - allowed_rad, i, center_y + allowed_rad ); SDL_SetRenderDrawColor( renderer.getRendererPtr(), std::get< 0 >( color ), std::get< 1 >( color ), std::get< 2 >( color ), 0x80 ); SDL_RenderDrawLine( renderer.getRendererPtr(), i, center_y - allowed_rad, i, center_y - allowed_rad + 2 ); SDL_RenderDrawLine( renderer.getRendererPtr(), i, center_y + allowed_rad, i, center_y + allowed_rad - 2 ); } SDL_SetRenderDrawColor( renderer.getRendererPtr(), 0xFF, 0, 0, 0xFF ); SDL_RenderDrawLine( renderer.getRendererPtr(), center_x, center_y, center_x + rad_, center_y ); SDL_RenderDrawLine( renderer.getRendererPtr(), center_x, center_y, center_x, center_y + rad_ ); SDL_RenderDrawLine( renderer.getRendererPtr(), center_x, center_y, center_x - rad_, center_y ); SDL_RenderDrawLine( renderer.getRendererPtr(), center_x, center_y, center_x, center_y - rad_ ); } virtual void render( Renderer &renderer ) override { std::vector< int > rect = { leftmost(), topmost(), rightmost(), bottommost() }; auto center_x = getX(); auto center_y = getY(); auto radsq = rad_ * rad_; for ( int i = rect[0]; i <= rect[2]; i++ ) { auto xdiff = center_x - i; auto xdist = xdiff * xdiff; auto allowed_rad = sqrt( radsq - xdist ); SDL_SetRenderDrawColor( renderer.getRendererPtr(), sdl_color.r, sdl_color.g, sdl_color.b, sdl_color.a ); SDL_RenderDrawLine( renderer.getRendererPtr(), i, center_y - allowed_rad, i, center_y + allowed_rad ); SDL_SetRenderDrawColor( renderer.getRendererPtr(), sdl_outline.r, sdl_outline.g, sdl_outline.b, sdl_outline.a ); SDL_RenderDrawLine( renderer.getRendererPtr(), i, center_y - allowed_rad, i, center_y - allowed_rad + 2 ); SDL_RenderDrawLine( renderer.getRendererPtr(), i, center_y + allowed_rad, i, center_y + allowed_rad - 2 ); } } private: int getRadius() const { return rad_; } double original_rad; int rad_; }; class LineRenderer : public RenderObject { public: LineRenderer() = delete; virtual ~LineRenderer(){}; LineRenderer( double x1, double y1, double x2, double y2, const std::shared_ptr< Renderer > &r ) : RenderObject( r ) { og_x1 = x1_ = x1; og_y1 = y1_ = y1; og_x2 = x2_ = x2; og_y2 = y2_ = y2; updateSizeAndPosition(); } LineRenderer( double x1, double y1, double x2, double y2, const std::shared_ptr< Renderer > &r, const std::string &color ) : LineRenderer( x1, y1, x2, y2, r ) { setColor( color ); } virtual void setColor( const std::string &color ) override { _color = getColorsHEX( color ); } virtual void specialAction( int /*UNUSED*/ ) override{}; virtual void render() override { if ( !getHidden() ) { SDL_SetRenderDrawColor( renderer->getRendererPtr(), std::get< 0 >( _color ), std::get< 1 >( _color ), std::get< 2 >( _color ), std::get< 3 >( _color ) ); SDL_RenderDrawLine( renderer->getRendererPtr(), pixel_x1, pixel_y1, pixel_x2, pixel_y2 ); } if ( hasCollisions() && renderer->getRenderColiders() && !getHidden() ) { for ( const auto &col : getCollisions() ) col->render( *renderer, colider_color ); } } virtual void move( int ticks ) override { if ( permanent ) return; auto addx = static_cast< double >( movementSpeed * movementDirection.first ) * ( static_cast< double >( ticks ) / 1000 ); auto addy = static_cast< double >( movementSpeed * movementDirection.second ) * ( static_cast< double >( ticks ) / 1000 ); if ( std::isnan( addx ) || std::isnan( addy ) ) return; og_x1 += addx; og_x2 += addx; og_y1 += addy; og_y2 += addy; custom_move( ticks ); updateSizeAndPosition(); } virtual void custom_move( int /*UNUSED*/ ) override {} virtual void setPos( double x, double y ) override { auto diffx = og_x2 - og_x1; auto diffy = og_y2 - og_y1; og_x1 = x; og_y1 = y; og_x2 = og_x1 + diffx; og_y2 = og_y1 + diffy; updateSizeAndPosition(); } virtual void setPos(const std::pair &pos) override { setPos(pos.first, pos.second); } virtual std::pair< double, double > getPos() const override { return { og_x1, og_y1 }; } virtual int leftmost() override { return pixel_x1 < pixel_x2 ? pixel_x1 : pixel_x2; } virtual int topmost() override { return pixel_y1 < pixel_y2 ? pixel_y1 : pixel_y2; } virtual int rightmost() override { return pixel_x1 > pixel_x2 ? pixel_x1 : pixel_x2; } virtual int bottommost() override { return pixel_y1 > pixel_y2 ? pixel_y1 : pixel_y2; } virtual int collisionPushX() override { return leftmost(); } virtual int collisionPushY() override { return topmost(); } virtual int collisionWidth() override { return rightmost() - leftmost(); } virtual int collisionHeight() override { return bottommost() - topmost(); } virtual void updateSizeAndPosition() override { updateXY(); auto dimension = renderer->getSmallerSide(); pixel_x1 = std::round(x1_ * dimension); pixel_x2 = std::round(x2_ * dimension); pixel_y1 = std::round(y1_ * dimension); pixel_y2 = std::round(y2_ * dimension); for ( auto &x : collisions ) { x->updateCollision( collisionPushX(), collisionPushY(), collisionWidth(), collisionHeight() ); } } virtual void centerX() override { centerx = true; updateSizeAndPosition(); } virtual std::shared_ptr copySelf() override { // TODO ACTUALLY copy, don't just copy pointers to textures and whatnot, create new textures!!! return std::make_shared(*this); } virtual SDL_Rect getRect() override { return {leftmost(), topmost(), rightmost() - leftmost(), bottommost() - topmost()}; } virtual std::pair< std::pair< double, double >, std::pair< double, double > > getDoubleRect() const override { return {{og_x1, og_y1}, {og_x2 - og_x1, og_y2 - og_y1}}; } void setOutlineColor( const std::string &/*UNUSED*/ ) override {} protected: void updateXY() { if ( !centerx ) { x1_ = og_x1; y1_ = og_y1; x2_ = og_x2; y2_ = og_y2; return; } auto width = renderer->getWidth(); auto height = renderer->getHeight(); if ( width > height ) { auto multiplier = static_cast< double >( width ) / static_cast< double >( height ); x1_ = og_x1 + static_cast< double >( multiplier - 1 ) / 2; x2_ = og_x2 + static_cast< double >( multiplier - 1 ) / 2; } else { x1_ = og_x1; x2_ = og_x2; } y1_ = og_y1; y2_ = og_y2; } double og_x1; double og_y1; double x1_; double y1_; double og_x2; double og_y2; double x2_; double y2_; int pixel_x1{}; int pixel_y1{}; int pixel_x2{}; int pixel_y2{}; bool centerx = false; std::tuple< int, int, int, int > _color; }; class RectangleRender : public RenderObject { public: RectangleRender() = delete; virtual ~RectangleRender(){}; RectangleRender( double x, double y, double w, double h, const std::shared_ptr< Renderer > &r ) : RenderObject( r ) { og_x = x_ = x; og_y = y_ = y; og_w = w_ = w; og_h = h_ = h; updateSizeAndPosition(); } RectangleRender( double x, double y, double w, double h, const std::shared_ptr< Renderer > &r, const std::shared_ptr< Texture > &t ) : RectangleRender( x, y, w, h, r ) { setTexture( t ); } RectangleRender( double x, double y, double w, double h, const std::shared_ptr< Renderer > &r, const std::string &img_or_color, bool is_polygon = false ) : RectangleRender( x, y, w, h, r ) { if ( !is_polygon ) { setTexture( img_or_color ); } else { setColor( img_or_color ); color = img_or_color; } } virtual void setColor( const std::string &color ) override { if ( !polygon ) { polygon = std::make_shared< Rect >( 0, 0, 1, 1 ); polygon->updateCollision( collisionPushX(), collisionPushY(), collisionWidth(), collisionHeight() ); } polygon->setColor( color ); } virtual void setOutlineColor( const std::string &color ) override { if ( !polygon ) { polygon = std::make_shared< Rect >( 0, 0, 1, 1 ); polygon->updateCollision( collisionPushX(), collisionPushY(), collisionWidth(), collisionHeight() ); } polygon->setOutlineColor( color ); } virtual void specialAction( int /*UNUSED*/ ) override{}; virtual void render() override { if ( !getHidden() ) { if ( polygon ) polygon->render( *renderer ); if ( texture != NULL ) SDL_RenderCopy( renderer->getRendererPtr(), texture->getTexturePtr(), NULL, &rect ); } if ( hasCollisions() && renderer->getRenderColiders() && !getHidden() ) { for ( const auto &col : getCollisions() ) col->render( *renderer, colider_color ); } } virtual void move( int ticks ) override { if ( permanent ) return; auto addx = static_cast< double >( movementSpeed * movementDirection.first ) * ( static_cast< double >( ticks ) / 1000 ); auto addy = static_cast< double >( movementSpeed * movementDirection.second ) * ( static_cast< double >( ticks ) / 1000 ); if ( std::isnan( addx ) || std::isnan( addy ) ) return; og_x += addx; og_y += addy; custom_move( ticks ); updateSizeAndPosition(); } virtual void custom_move( int /*UNUSED*/ ) override {} virtual std::pair< std::pair< double, double >, std::pair< double, double > > getDoubleRect() const override { return { { og_x, og_y }, { og_w, og_h } }; } virtual void setPos( double x, double y ) override { og_x = x; og_y = y; updateSizeAndPosition(); } virtual void setPos(const std::pair &pos) override { setPos(pos.first, pos.second); } virtual std::pair< double, double > getPos() const override { return { og_x, og_y }; } virtual int leftmost() override { return rect.x; } virtual int topmost() override { return rect.y; } virtual int rightmost() override { return rect.x + rect.w; } virtual int bottommost() override { return rect.y + rect.h; } virtual int collisionPushX() override { return rect.x; } virtual int collisionPushY() override { return rect.y; } virtual int collisionWidth() override { return rect.w; } virtual int collisionHeight() override { return rect.h; } virtual void updateSizeAndPosition() override { updateXY(); auto dimension = renderer->getSmallerSide(); rect.x = std::round(x_ * dimension); rect.y = std::round(y_ * dimension); rect.w = std::round((x_ + w_) * dimension) - rect.x; rect.h = std::round((y_ + h_) * dimension) - rect.y; if ( polygon ) polygon->updateCollision( collisionPushX(), collisionPushY(), collisionWidth(), collisionHeight() ); for ( auto &x : collisions ) { x->updateCollision( collisionPushX(), collisionPushY(), collisionWidth(), collisionHeight() ); } } virtual SDL_Rect getRect() override { return rect; } virtual void centerX() override { centerx = true; updateSizeAndPosition(); } virtual std::shared_ptr copySelf() override { // TODO ACTUALLY copy, don't just copy pointers to textures and whatnot, create new textures!!! return std::make_shared(*this); } std::string getColor() const { return color; } protected: void updateXY() { if ( !centerx ) { x_ = og_x; y_ = og_y; return; } auto width = renderer->getWidth(); auto height = renderer->getHeight(); if ( width > height ) { auto multiplier = static_cast< double >( width ) / static_cast< double >( height ); x_ = og_x + static_cast< double >( multiplier - 1 ) / 2; } else { x_ = og_x; } y_ = og_y; } double og_x; double og_y; double og_w; double og_h; double x_; double y_; double w_; double h_; bool centerx = false; SDL_Rect rect; std::string color = ""; }; class TextRenderer : public RectangleRender { public: TextRenderer() = delete; TextRenderer( double x, double y, double w, double h, std::shared_ptr< Renderer > &r ) : RectangleRender( x, y, w, h, r ) {} TextRenderer( double x, double y, double w, double h, std::shared_ptr< Renderer > &r, Font &font, const std::string &text, const std::string &color = "FFFFFF", const std::string &outline_color = "000000", int outline_size = -1, int flags = SDLPP_TEXT_CENTER ) : RectangleRender( x, y, w, h, r ) { position_flags = flags; setText( font, text, color, outline_color, outline_size ); } void setText( Font &font, const std::string &text, const std::string &color = "FFFFFF", const std::string &outline_color = "000000", int outline_size = -1 ) { _text = text; setTextColor(font, color, outline_color, outline_size); } void setTextColor( Font &font, const std::string &color = "FFFFFF", const std::string &outline_color = "000000", int outline_size = -1) { setTexture( font, _text, color, outline_color, outline_size ); updateDstRect(); } void changeText( const std::string &text ) { _text = text; } void setFlags( int flags ) { position_flags = flags; updateDstRect(); } virtual void render() override { if ( !getHidden() ) { if ( polygon ) polygon->render( *renderer ); if ( texture != NULL ) SDL_RenderCopy( renderer->getRendererPtr(), texture->getTexturePtr(), NULL, &dst_rect ); } if ( hasCollisions() && renderer->getRenderColiders() && !getHidden() ) { for ( const auto &col : getCollisions() ) col->render( *renderer, colider_color ); } } virtual void updateSizeAndPosition() override { RectangleRender::updateSizeAndPosition(); updateDstRect(); } virtual std::shared_ptr copySelf() override { // TODO ACTUALLY copy, don't just copy pointers to textures and whatnot, create new textures!!! return std::make_shared(*this); } private: void updateDstRect() { if ( !texture ) return; int text_width{}, text_height{}; SDL_QueryTexture( texture->getTexturePtr(), NULL, NULL, &text_width, &text_height ); if ( text_width < rect.w && text_height < rect.h ) { dst_rect.w = text_width; dst_rect.h = text_height; } else { double x_div = static_cast< double >( text_width ) / static_cast< double >( rect.w ); double y_div = static_cast< double >( text_height ) / static_cast< double >( rect.h ); if ( x_div > y_div ) { dst_rect.w = text_width / x_div; dst_rect.h = text_height / x_div; } else { dst_rect.w = text_width / y_div; dst_rect.h = text_height / y_div; } } if ( !( position_flags & SDLPP_TEXT_LEFT || position_flags & SDLPP_TEXT_RIGHT ) ) { dst_rect.x = rect.x + ( rect.w - dst_rect.w ) / 2; } else if ( position_flags & SDLPP_TEXT_LEFT ) { dst_rect.x = rect.x; } else if ( position_flags & SDLPP_TEXT_RIGHT ) { dst_rect.x = rect.x + rect.w - dst_rect.w; } if ( !( position_flags & SDLPP_TEXT_TOP || position_flags & SDLPP_TEXT_BOTTOM ) ) { dst_rect.y = rect.y + ( rect.h - dst_rect.h ) / 2; } else if ( position_flags & SDLPP_TEXT_TOP ) { dst_rect.y = rect.y; } else if ( position_flags & SDLPP_TEXT_BOTTOM ) { dst_rect.y = rect.y + rect.h - dst_rect.h; } } std::string _text{}; int position_flags = 0; SDL_Rect dst_rect{}; }; class CircleRender : public RenderObject { public: CircleRender() = delete; virtual ~CircleRender(){}; CircleRender( int x, int y, int rad, std::shared_ptr< Renderer > &r ) : RenderObject( r ) { x_ = x; y_ = y; rad_ = rad; } CircleRender( int x, int y, int rad, std::shared_ptr< Renderer > &r, std::shared_ptr< Texture > &t ) : CircleRender( x, y, rad, r ) { setTexture( t ); } CircleRender( int x, int y, int rad, std::shared_ptr< Renderer > &r, const std::string &img_path ) : CircleRender( x, y, rad, r ) { auto texture = std::make_shared< Texture >( r, img_path ); setTexture( texture ); } virtual void render(); virtual int leftmost() { return x_ - rad_; } virtual int topmost() { return y_ - rad_; } virtual int collisionPushX() { return x_; } virtual int collisionPushY() { return y_; } private: int x_; int y_; int rad_; }; bool init(); bool init( uint32_t SDL_OPTIONS ); bool init( uint32_t SDL_OPTIONS, int IMAGE_OPTIONS ); template < class T > void testPolymorphism( T &obj ); } // end of namespace SDLPP #endif