spacebox/src/Box.hpp

227 lines
8.0 KiB
C++

/* +------------------------------------------------------+
____/ \____ /| - Open source game framework licensed to freely use, |
\ / / | copy, modify and sell without restriction |
+--\ ^__^ /--+ | |
| ~/ \~ | | - created for <https://foam.shampoo.ooo> |
| ~~~~~~~~~~~~ | +------------------------------------------------------+
| SPACE ~~~~~ | /
| ~~~~~~~ BOX |/
+-------------*/
#pragma once
#include <iostream>
#include <algorithm>
#include <stdexcept>
#include <functional>
#include <optional>
#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<std::reference_wrapper<glm::vec2>> 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<std::reference_wrapper<Box>> 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"