/* +------------------------------------------------------+ ____/ \____ /| - Open source game framework licensed to freely use, | \ / / | copy, modify and sell without restriction | +--\ ^__^ /--+ | | | ~/ \~ | | - created for | | ~~~~~~~~~~~~ | +------------------------------------------------------+ | SPACE ~~~~~ | / | ~~~~~~~ BOX |/ +-------------*/ #pragma once #include #include #include #include #include #include "SDL.h" #define GLM_ENABLE_EXPERIMENTAL #include "glm/common.hpp" #include "glm/vec2.hpp" class Segment; /*! * A ::Box object represents a rectangle, a two-dimensional four-sided polygon with two parallel sides. * * Rotating by three dimensions into the Z-dimension is not currently supported and must be implemented by the user, * but 3D transformation is planned to be added in a future update. */ class Box : public SDL_FRect { private: bool invert_y_state = true; public: /*! * Construct a Box object by giving a corner coordinate (top left if Y-axis is not inverted, bottom left otherwise) and * size (as width, height). The invert_y argument indicates that the Y-coordinate increases from the bottom of the screen * to the top (like in OpenGL) rather than from the top to the bottom (like in Pygame). * * @param corner top left coordinate in the x, y axis * @param size size of the box in width and height * @param invert_y if true, y-coordinates increase as they move up on the screen, rather than decrease */ Box(const glm::vec2& corner = {0.0f, 0.0f}, const glm::vec2& size = {0.0f, 0.0f}, bool invert_y = true); /*! * Construct a Box object lfrom float arguments: x, y, width, height. If invert_y is set, the Y-axis increases from bottom to * top instead of top to bottom. * * @see Box::Box(const glm::vec2&, const glm::vec2&, bool) * * @param x * @param y * @param width size of the x-dimension * @param height size of the y-dimension * @param invert_y if true, y-coordinates increase as they move up on the screen, rather than decrease */ Box(float x, float y, float width, float height, bool invert_y = true); /*! * Construct a ::Box object by passing an SDL_Rect struct, which is of the form {x, y, w, h} and limited to int arguments. * * @param rect see SDL's rect documentation http://doc.here/sdlwiki/SDL_FRect.html * @param invert_y if true, y-coordinates increase as they move up on the screen, rather than decrease */ Box(const SDL_Rect& rect, bool invert_y = true); void invert_y(bool); bool inverted_y() const; float width() const; void width(float); float height() const; void height(float); /*! * @return the size as a vector `{width, height}` */ glm::vec2 size() const; /*! * Set the size. Negative values will be clamped to zero. * * @param size new size in width, height * @param preserve_center if true, expand or contract the box from the center, leaving its center value unchanged */ void size(const glm::vec2& size, bool preserve_center = false); /*! * If the flag is not set, this will return width divided by height. Otherwise, it will check which side is longer and return the longer side * divided by the shorter side. * * @param larger_ratio if true, divide by the shorter side, causing the result to always be 1.0 or greater */ float aspect(bool larger_ratio = false) const; float area() const; float top() const; float right() const; float bottom() const; float left() const; float cx() const; float cy() const; void top(float, bool = false); void drag_top(float); void right(float, bool = false); void drag_right(float); void bottom(float, bool = false); void drag_bottom(float); void left(float, bool = false); void drag_left(float); void cx(float); void cy(float); glm::vec2 nw() const; glm::vec2 north() const; glm::vec2 ne() const; glm::vec2 east() const; glm::vec2 se() const; glm::vec2 south() const; glm::vec2 sw() const; glm::vec2 west() const; glm::vec2 center() const; void nw(const glm::vec2&); void north(const glm::vec2&); void ne(const glm::vec2&); void east(const glm::vec2&); void se(const glm::vec2&); void south(const glm::vec2&); void sw(const glm::vec2&); void west(const glm::vec2&); void center(const glm::vec2&); /*! * Called when a Box instance is converted into an SDL_Rect. Allows passing Box to SDL functions that * expect an SDL_Rect instead of an SDL_FRect. Corner values are converted from float to int using a static * cast. * * @return box with all corners cast to int and converted to SDL_Rect */ operator SDL_Rect() const; void clear(); /*! * Scale the box by multiplying size by {delta.x, delta.y}. If preserve center is set, the box will be scaled * around the center, otherwise the top left corner will remain in place. * * @param delta amount to scale * @param preserve_center after scaling, move the box so the center is the same as it was before scaling */ void scale(glm::vec2 delta, bool preserve_center = false); /*! * Scale the box by multiplying both width and height by delta. If preserve center is set, the box will be scaled * around the center, otherwise the top left corner will remain in place. * * @param delta amount to scale * @param preserve_center after scaling, move the box so the center is the same as it was before scaling */ void scale(float delta, bool preserve_center = false); void expand(glm::vec2, bool = false); void expand(float, bool = false); void move(const glm::vec2&); Box stamp(const glm::vec2&) const; bool fits(const Box&) const; void crop(const Box&); /*! * Collide a 2D point with the box. Includes the edges, so points on the edges will return `true`. * * @return `true` if point is inside the box, `false` otherwise */ bool collide(const glm::vec2& point) const; /*! * Collide a line segment with the box. If intrseection is passed and there is a collision, intersection will be filled with * the coordinates of the first intersection found unless the segment is fully within the box not touching any edges. * * @param segment line segment to collide with the box * @param intersection optional reference to 2D vector to fill with the first intersection point * @return `true` if segment collides with the box, `false` otherwies */ bool collide(const Segment& segment, std::optional> intersection = std::nullopt) const; /*! * Collide with another box object. If overlap is passed, it is set to the box representing the area where the two boxes overlap. * * @param box another box object to collide with * @param overlap optional reference to a box object to be filled with the overlapping area * @return `true` if box collides with the passed box, `false` otherwise */ bool collide(const Box& box, std::optional> overlap = std::nullopt) const; std::string string() const; /*! * Convert the box to a string when it is passed in a string context. * * @return string representation of the box */ operator std::string() const; }; namespace std { std::ostream& operator<<(std::ostream&, const Box&); } /* Add Box class to the sb namespace. This should be the default location, but Box is left in the global namespace * for backward compatibility. */ namespace sb { using ::Box; } #include "extension.hpp" #include "Segment.hpp"