585 lines
15 KiB
C++
585 lines
15 KiB
C++
/* /\ +--------------------------------------------------------------+
|
|
____/ \____ /| - zlib/MIT/Unlicenced game framework licensed to freely use, |
|
|
\ / / | copy, and modify without restriction |
|
|
+--\ ^__^ /--+ | |
|
|
| ~/ \~ | | - originally created at [http://nugget.fun] |
|
|
| ~~~~~~~~~~~~ | +--------------------------------------------------------------+
|
|
| SPACE ~~~~~ | /
|
|
| ~~~~~~~ BOX |/
|
|
+-------------*/
|
|
|
|
#include "Box.hpp"
|
|
|
|
Box::Box(const glm::vec2& corner, const glm::vec2& size, bool invert_y)
|
|
{
|
|
x = corner.x;
|
|
y = corner.y;
|
|
w = size.x;
|
|
h = size.y;
|
|
this->invert_y(invert_y);
|
|
}
|
|
|
|
Box::Box(float x, float y, float width, float height, bool invert_y) : Box({x, y}, {width, height}, invert_y) {}
|
|
|
|
Box::Box(const SDL_Rect& rect, bool invert_y) : Box({rect.x, rect.y}, {rect.w, rect.h}, invert_y) {}
|
|
|
|
/* Set inverted Y mode to true, meaning the Y-coordinate increases from the bottom to the top, or false, meaning the
|
|
* Y-coordinate increases from the top to the bottom. */
|
|
void Box::invert_y(bool invert)
|
|
{
|
|
invert_y_state = invert;
|
|
}
|
|
|
|
/* Returns true if Y-axis was set to inverted, meaning the Y-coordinate increases from the bottom to the top, or false,
|
|
* meaning Y-coordinate increases from the top to the bottom. */
|
|
bool Box::inverted_y() const
|
|
{
|
|
return invert_y_state;
|
|
}
|
|
|
|
/* Return the width */
|
|
float Box::width() const
|
|
{
|
|
return w;
|
|
}
|
|
|
|
/* Set the width. Negative values will be clamped to zero. */
|
|
void Box::width(float width)
|
|
{
|
|
w = std::max(width, 0.0f);
|
|
}
|
|
|
|
/* Return the height */
|
|
float Box::height() const
|
|
{
|
|
return h;
|
|
}
|
|
|
|
/* Set the height. Negative values will be clamped to zero. */
|
|
void Box::height(float height)
|
|
{
|
|
h = std::max(height, 0.0f);
|
|
}
|
|
|
|
glm::vec2 Box::size() const
|
|
{
|
|
return {width(), height()};
|
|
}
|
|
|
|
void Box::size(const glm::vec2& size, bool preserve_center)
|
|
{
|
|
glm::vec2 center = this->center();
|
|
width(size.x);
|
|
height(size.y);
|
|
if (preserve_center)
|
|
{
|
|
this->center(center);
|
|
}
|
|
}
|
|
|
|
float Box::aspect(bool larger_ratio) const
|
|
{
|
|
if (!larger_ratio || width() > height())
|
|
{
|
|
return width() / height();
|
|
}
|
|
else
|
|
{
|
|
return height() / width();
|
|
}
|
|
}
|
|
|
|
/* Return the area of the box (width * height) */
|
|
float Box::area() const
|
|
{
|
|
return width() * height();
|
|
}
|
|
|
|
/* Return the y coordinate representing the top of the box */
|
|
float Box::top() const
|
|
{
|
|
/* if Y-coordinate increases from bottom to top, use the bottom to determine this value */
|
|
if (inverted_y())
|
|
{
|
|
return bottom() + height();
|
|
}
|
|
/* otherwise use the corner Y-coordinate */
|
|
else
|
|
{
|
|
return y;
|
|
}
|
|
}
|
|
|
|
/* Return the x coordinate representing the right side of the box */
|
|
float Box::right() const
|
|
{
|
|
return left() + width();
|
|
}
|
|
|
|
/* Return the y coordinate representing the bottom of the box */
|
|
float Box::bottom() const
|
|
{
|
|
/* if Y-coordinate increases from bottom to top, use the stored Y-coordinate */
|
|
if (inverted_y())
|
|
{
|
|
return y;
|
|
}
|
|
/* otherwise use the top to determine the bottom value */
|
|
else
|
|
{
|
|
return top() + height();
|
|
}
|
|
}
|
|
|
|
/* Return the x coordinate representing the left side of the box */
|
|
float Box::left() const
|
|
{
|
|
return x;
|
|
}
|
|
|
|
/* Return the x component of the center coordinates */
|
|
float Box::cx() const
|
|
{
|
|
return left() + width() / 2.0f;
|
|
}
|
|
|
|
/* Return the y component of the center coordinates */
|
|
float Box::cy() const
|
|
{
|
|
/* if Y-coordinate increases from bottom to top, add from the bottom */
|
|
if (inverted_y())
|
|
{
|
|
return bottom() + height() / 2.0f;
|
|
}
|
|
/* otherwise add from the top */
|
|
else
|
|
{
|
|
return top() + height() / 2.0f;
|
|
}
|
|
}
|
|
|
|
/* Set the y coordinate of the top of the box. If drag is set, the other sides of the box will remain in the
|
|
* same position, and the top will be moved, altering the size of the box. Otherwise, the box will remain the
|
|
* same size, and the bottom location will change, moving the box but preserving the size. */
|
|
void Box::top(float top, bool drag)
|
|
{
|
|
float delta = top - this->top();
|
|
if (!drag)
|
|
{
|
|
move({0, delta});
|
|
}
|
|
else
|
|
{
|
|
drag_top(delta);
|
|
}
|
|
}
|
|
|
|
/* Drag the top delta amount, leaving the bottom in the same position and altering the size of the box. The top
|
|
* can be dragged past the bottom, in which case the current bottom will become the new top value. */
|
|
void Box::drag_top(float delta)
|
|
{
|
|
float new_location = top() + delta;
|
|
height(std::abs(bottom() - new_location));
|
|
if ((inverted_y() && new_location < bottom()) || (!inverted_y() && new_location > bottom()))
|
|
{
|
|
bottom(new_location);
|
|
}
|
|
else
|
|
{
|
|
top(new_location);
|
|
}
|
|
}
|
|
|
|
/* Set the x coordinate of the right side of the box. If drag is set, the other sides of the box will remain in the
|
|
* same position, and the right will be moved, altering the size of the box. Otherwise, the box will remain the
|
|
* same size, and the left location will change, moving the box but preserving the size. */
|
|
void Box::right(float right, bool drag)
|
|
{
|
|
float delta = right - this->right();
|
|
if (!drag)
|
|
{
|
|
move({delta, 0});
|
|
}
|
|
else
|
|
{
|
|
drag_right(delta);
|
|
}
|
|
}
|
|
|
|
/* Drag the right side delta amount, leaving the left in the same position and altering the size of the box. The right
|
|
* side can be dragged past the left, in which case the current left will become the new right value. */
|
|
void Box::drag_right(float delta)
|
|
{
|
|
float new_location = right() + delta;
|
|
width(std::abs(left() - new_location));
|
|
if (new_location < left())
|
|
{
|
|
left(new_location);
|
|
}
|
|
}
|
|
|
|
/* Set the y coordinate of the bottom of the box. If drag is set, the other sides of the box will remain in the
|
|
* same position, and the bottom will be moved, altering the size of the box. Otherwise, the box will remain the
|
|
* same size, and the top location will change, moving the box but preserving the size. */
|
|
void Box::bottom(float bottom, bool drag)
|
|
{
|
|
float delta = bottom - this->bottom();
|
|
if (!drag)
|
|
{
|
|
move({0, delta});
|
|
}
|
|
else
|
|
{
|
|
drag_bottom(delta);
|
|
}
|
|
}
|
|
|
|
/* Drag the bottom delta amount, leaving the top in the same position and altering the size of the box. The bottom
|
|
* can be dragged past the top, in which case the current top will become the new bottom value. */
|
|
void Box::drag_bottom(float delta)
|
|
{
|
|
float new_location = bottom() + delta;
|
|
height(std::abs(top() - new_location));
|
|
if ((inverted_y() && new_location > top()) || (!inverted_y() && new_location < bottom()))
|
|
{
|
|
top(new_location);
|
|
}
|
|
else
|
|
{
|
|
bottom(new_location);
|
|
}
|
|
}
|
|
|
|
/* Set the x coordinate of the left side of the box. If drag is set, the other sides of the box will remain in the
|
|
* same position, and the left will be moved, altering the size of the box. Otherwise, the box will remain the
|
|
* same size, and the right location will change, moving the box but preserving the size. */
|
|
void Box::left(float left, bool drag)
|
|
{
|
|
float delta = left - this->left();
|
|
if (!drag)
|
|
{
|
|
move({delta, 0});
|
|
}
|
|
else
|
|
{
|
|
drag_left(delta);
|
|
}
|
|
}
|
|
|
|
/* Drag the left side delta amount, leaving the right side in the same position and altering the size of the box.
|
|
* The left side can be dragged past the right, in which case the current right will become the new left value. */
|
|
void Box::drag_left(float delta)
|
|
{
|
|
float new_location = left() + delta;
|
|
if (new_location > right())
|
|
{
|
|
left(right());
|
|
width(new_location - left());
|
|
}
|
|
else
|
|
{
|
|
width(right() - new_location);
|
|
left(new_location);
|
|
}
|
|
}
|
|
|
|
/* Set the center x component */
|
|
void Box::cx(float cx)
|
|
{
|
|
move({cx - this->cx(), 0.0f});
|
|
}
|
|
|
|
/* Set the center y component */
|
|
void Box::cy(float cy)
|
|
{
|
|
move({0.0f, cy - this->cy()});
|
|
}
|
|
|
|
/* Return the coordinates of the top left corner */
|
|
glm::vec2 Box::nw() const
|
|
{
|
|
return {left(), top()};
|
|
}
|
|
|
|
/* Return the coordinates of the center of the top edge */
|
|
glm::vec2 Box::north() const
|
|
{
|
|
return {left() + width() / 2.0f, top()};
|
|
}
|
|
|
|
/* Return the coordinates of the top right corner */
|
|
glm::vec2 Box::ne() const
|
|
{
|
|
return {right(), top()};
|
|
}
|
|
|
|
/* Return the coordinates of the center of the right edge */
|
|
glm::vec2 Box::east() const
|
|
{
|
|
return {right(), top() + height() / 2.0f};
|
|
}
|
|
|
|
/* Return the coordinates of the bottom right corner */
|
|
glm::vec2 Box::se() const
|
|
{
|
|
return {right(), bottom()};
|
|
}
|
|
|
|
/* Return the coordinates of the center of the bottom edge */
|
|
glm::vec2 Box::south() const
|
|
{
|
|
return {left() + width() / 2, bottom()};
|
|
}
|
|
|
|
/* Return the coordinates of the bottom left edge */
|
|
glm::vec2 Box::sw() const
|
|
{
|
|
return {left(), bottom()};
|
|
}
|
|
|
|
/* Return the coordinates of the center of the left edge */
|
|
glm::vec2 Box::west() const
|
|
{
|
|
return {left(), top() + height() / 2.0f};
|
|
}
|
|
|
|
/* Return the center coordinates */
|
|
glm::vec2 Box::center() const
|
|
{
|
|
return {cx(), cy()};
|
|
}
|
|
|
|
/* Move the box by specifying the top left corner coordinates */
|
|
void Box::nw(const glm::vec2& nw)
|
|
{
|
|
move(nw - this->nw());
|
|
}
|
|
|
|
/* Move the box by specifying the center of the top edge */
|
|
void Box::north(const glm::vec2& north)
|
|
{
|
|
move(north - this->north());
|
|
}
|
|
|
|
/* Move the box by specifying the top right corner coordinates */
|
|
void Box::ne(const glm::vec2& ne)
|
|
{
|
|
move(ne - this->ne());
|
|
}
|
|
|
|
/* Move the box by specifying the center of the right edge */
|
|
void Box::east(const glm::vec2& east)
|
|
{
|
|
move(east - this->east());
|
|
}
|
|
|
|
/* Move the box by specifying the bottom right corner coordinates */
|
|
void Box::se(const glm::vec2& se)
|
|
{
|
|
move(se - this->se());
|
|
}
|
|
|
|
/* Move the box by specifying the center of the bottom edge */
|
|
void Box::south(const glm::vec2& south)
|
|
{
|
|
move(south - this->south());
|
|
}
|
|
|
|
/* Move the box by specifying the bottom left corner coordinates */
|
|
void Box::sw(const glm::vec2& sw)
|
|
{
|
|
move(sw - this->sw());
|
|
}
|
|
|
|
/* Move the box by specifying the center of the left edge */
|
|
void Box::west(const glm::vec2& west)
|
|
{
|
|
move(west - this->west());
|
|
}
|
|
|
|
/* Move the box by specifying the center coordinates */
|
|
void Box::center(const glm::vec2& center)
|
|
{
|
|
move(center - this->center());
|
|
}
|
|
|
|
Box::operator SDL_Rect() const
|
|
{
|
|
return {static_cast<int>(left()), static_cast<int>(top()), static_cast<int>(width()), static_cast<int>(height())};
|
|
}
|
|
|
|
/* Zero out the values of the box coordinates and size */
|
|
void Box::clear()
|
|
{
|
|
x = 0;
|
|
y = 0;
|
|
w = 0;
|
|
h = 0;
|
|
}
|
|
|
|
void Box::scale(glm::vec2 delta, bool preserve_center)
|
|
{
|
|
glm::vec2 center = this->center();
|
|
size(this->size() * delta);
|
|
if (preserve_center)
|
|
{
|
|
this->center(center);
|
|
}
|
|
}
|
|
|
|
void Box::scale(float delta, bool preserve_center)
|
|
{
|
|
scale({delta, delta}, preserve_center);
|
|
}
|
|
|
|
/* Expand (or shrink by passing negative values) the box size by adding delta to the current size. If preserve center
|
|
* is set, the box will expand around the center, otherwise the top left corner will remain in place. */
|
|
void Box::expand(glm::vec2 delta, bool preserve_center)
|
|
{
|
|
glm::vec2 center = this->center();
|
|
size(size() + delta);
|
|
if (preserve_center)
|
|
{
|
|
this->center(center);
|
|
}
|
|
}
|
|
|
|
/* Expand (or shrink by passing negative values) the box size by adding delta to both width and height. If preserve
|
|
* center is set, the box will expand around the center, otherwise the top left corner will remain in place. */
|
|
void Box::expand(float delta, bool preserve_center)
|
|
{
|
|
expand({delta, delta}, preserve_center);
|
|
}
|
|
|
|
/* Move the box in the x and y plane by delta amount */
|
|
void Box::move(const glm::vec2& delta)
|
|
{
|
|
x += delta.x;
|
|
y += delta.y;
|
|
}
|
|
|
|
/* Return a copy of the original box, moved by delta amount in the x and y plane */
|
|
Box Box::stamp(const glm::vec2& delta) const
|
|
{
|
|
Box clone = *this;
|
|
clone.move(delta);
|
|
return clone;
|
|
}
|
|
|
|
/* Returns true if box fits completely inside container, false otherwise. Includes the sides
|
|
* of the box, so if the sides are equal, it is considered inside. */
|
|
bool Box::fits(const Box& container) const
|
|
{
|
|
return
|
|
!(
|
|
((inverted_y() && top() > container.top()) || (!inverted_y() && top() < container.top())) ||
|
|
right() > container.right() ||
|
|
((inverted_y() && bottom() < container.bottom()) || (!inverted_y() && bottom() > container.bottom())) ||
|
|
left() < container.left()
|
|
);
|
|
}
|
|
|
|
/* Removes any part of the box that is not inside the passed crop area box */
|
|
void Box::crop(const Box& area)
|
|
{
|
|
if ((inverted_y() && top() > area.top()) || (!inverted_y() && top() < area.top()))
|
|
{
|
|
top(area.top(), true);
|
|
}
|
|
if (right() > area.right())
|
|
{
|
|
right(area.right(), true);
|
|
}
|
|
if ((inverted_y() && bottom() < area.bottom()) || (!inverted_y() && bottom() > area.bottom()))
|
|
{
|
|
bottom(area.bottom(), true);
|
|
}
|
|
if (left() < area.left())
|
|
{
|
|
left(area.left(), true);
|
|
}
|
|
}
|
|
|
|
bool Box::collide(const glm::vec2& point) const
|
|
{
|
|
return point.x >= left() && point.x <= right() && point.y >= (inverted_y() ? bottom() : top()) && point.y <= (inverted_y() ? top() : bottom());
|
|
}
|
|
|
|
bool Box::collide(const Segment& segment, std::optional<std::reference_wrapper<glm::vec2>> intersection) const
|
|
{
|
|
/* Do the faster check of whether the square represented by the segment diagonal collides to determine whether to look more closely. */
|
|
if (collide(segment.box()))
|
|
{
|
|
/* Check if segment intersects any edges, storing the intersection point if so. */
|
|
if (segment.intersect({nw(), ne()}, intersection) || segment.intersect({ne(), se()}, intersection) ||
|
|
segment.intersect({sw(), se()}, intersection) || segment.intersect({nw(), sw()}, intersection))
|
|
{
|
|
return true;
|
|
}
|
|
/* Check if segment is fully inside the box. */
|
|
else if (collide(segment.start()) && collide(segment.end()))
|
|
{
|
|
return true;
|
|
}
|
|
/* Otherwise, segment must be outside the box even though its box has collided. */
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool Box::collide(const Box& box, std::optional<std::reference_wrapper<Box>> overlap) const
|
|
{
|
|
float top, bottom, h;
|
|
if (inverted_y())
|
|
{
|
|
top = std::min(this->top(), box.top());
|
|
bottom = std::max(this->bottom(), box.bottom());
|
|
h = top - bottom;
|
|
}
|
|
else
|
|
{
|
|
top = std::max(this->top(), box.top());
|
|
bottom = std::min(this->bottom(), box.bottom());
|
|
h = bottom - top;
|
|
}
|
|
float right = std::min(this->right(), box.right());
|
|
float left = std::max(this->left(), box.left());
|
|
float w = right - left;
|
|
bool collide = w > 0 && h > 0;
|
|
if (collide && overlap.has_value())
|
|
{
|
|
Box& overlap_box = overlap.value();
|
|
overlap_box.left(left);
|
|
overlap_box.top(top);
|
|
overlap_box.width(w);
|
|
overlap_box.height(h);
|
|
}
|
|
return collide;
|
|
}
|
|
|
|
/* Return the string representation of a Box "{left, top, width, height}" */
|
|
std::string Box::string() const
|
|
{
|
|
std::stringstream output;
|
|
output << "{(" << left() << ", " << top() << "), (" << width() << ", " << height() << ")}";
|
|
return output.str();
|
|
}
|
|
|
|
Box::operator std::string() const
|
|
{
|
|
return string();
|
|
}
|
|
|
|
/* Feed a string representation of the box to the passed ostream */
|
|
std::ostream& std::operator<<(std::ostream& out, const Box& box)
|
|
{
|
|
out << box.string();
|
|
return out;
|
|
}
|