game/sdlpp.hpp
2020-09-11 21:38:33 +02:00

1390 lines
47 KiB
C++

// TODO mutex guard instead of lock/unlock
#ifndef SDLPP_HPP
#define SDLPP_HPP
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_ttf.h>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <limits>
#include <memory>
#include <mutex>
#include <unordered_set>
#include <vector>
#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<double, double> &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<RenderObject> copySelf() = 0;
bool isStatic() {
return is_static;
}
void setStatic(bool stat = true) {
is_static = stat;
}
std::shared_ptr<Renderer> 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<std::mutex> 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<RenderObject> &obj ) {
moveZ(obj, -1);
}
void moveUpZ( const std::shared_ptr<RenderObject> &obj ) {
moveZ(obj, 1);
}
void moveZ( const std::shared_ptr<RenderObject> &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<Renderer> 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<double, double> &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<RenderObject> copySelf() override {
// TODO ACTUALLY copy, don't just copy pointers to textures and whatnot, create new textures!!!
return std::make_shared<LineRenderer>(*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<double, double> &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<RenderObject> copySelf() override {
// TODO ACTUALLY copy, don't just copy pointers to textures and whatnot, create new textures!!!
return std::make_shared<RectangleRender>(*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<RenderObject> copySelf() override {
// TODO ACTUALLY copy, don't just copy pointers to textures and whatnot, create new textures!!!
return std::make_shared<TextRenderer>(*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