spacebox/src/Box.cpp

533 lines
14 KiB
C++

#include "extension.hpp"
#include "Segment.hpp"
#include "Box.hpp"
/* Construct a Box by giving the (x, y) coordinate of the NW point and the size as a 2D vector (width, height) */
Box::Box(const glm::vec2& nw, const glm::vec2& size)
{
x = nw.x;
y = nw.y;
w = size.x;
h = size.y;
}
/* Construct a Box by passing an SDL_Rect struct, which is of the form {x, y, w, h} and limited to int arguments */
Box::Box(const SDL_Rect& rect) : Box({rect.x, rect.y}, {rect.w, rect.h}) {}
/* 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);
}
/* Return the size as a vector {width, height} */
glm::vec2 Box::size() const
{
return glm::vec2(width(), height());
}
/* Set the size. Negative values will be clamped to zero. */
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);
}
}
/* Returns width divided by height. Returns a value regardless of which side is longer, so it can return values
* greater or less than 0. */
float Box::aspect() const
{
return width() / height();
}
/* 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
{
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
{
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;
}
/* Return the y component of the center coordinates */
float Box::cy() const
{
return top() + height() / 2;
}
/* 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)
{
if (!drag)
{
y = top;
}
else
{
drag_top(top - this->top());
}
}
/* 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;
if (new_location > bottom())
{
top(bottom());
height(new_location - top());
}
else
{
height(bottom() - new_location);
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 (new_location < top())
{
top(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)
{
if (!drag)
{
x = left;
}
else
{
drag_left(left - this->left());
}
}
/* 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 x)
{
move({x - cx(), 0.0f});
}
/* Set the center y component */
void Box::cy(float y)
{
move({0.0f, y - 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 glm::vec2(left() + width() / 2.0f, top());
}
/* Return the coordinates of the top right corner */
glm::vec2 Box::ne() const
{
return glm::vec2(right(), top());
}
/* Return the coordinates of the center of the right edge */
glm::vec2 Box::east() const
{
return glm::vec2(right(), top() + height() / 2.0f);
}
/* Return the coordinates of the bottom right corner */
glm::vec2 Box::se() const
{
return glm::vec2(right(), bottom());
}
/* Return the coordinates of the center of the bottom edge */
glm::vec2 Box::south() const
{
return glm::vec2(left() + width() / 2, bottom());
}
/* Return the coordinates of the bottom left edge */
glm::vec2 Box::sw() const
{
return glm::vec2(left(), bottom());
}
/* Return the coordinates of the center of the left edge */
glm::vec2 Box::west() const
{
return glm::vec2(left(), top() + height() / 2);
}
/* Return the center coordinates */
glm::vec2 Box::center() const
{
return glm::vec2(left() + width() / 2, top() + height() / 2);
}
/* 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());
}
/* 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 */
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()
{
nw(glm::vec2(0, 0));
size(glm::vec2(0, 0));
}
/* 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. */
void Box::scale(glm::vec2 delta, bool preserve_center)
{
glm::vec2 center = this->center();
size(this->size() * delta);
if (preserve_center)
{
this->center(center);
}
}
/* 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. */
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)
{
left(left() + delta.x);
top(top() + 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 !(top() < container.top() || right() > container.right() ||
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 (top() < area.top())
{
top(area.top(), true);
}
if (right() > area.right())
{
right(area.right(), true);
}
if (bottom() > area.bottom())
{
bottom(area.bottom(), true);
}
if (left() < area.left())
{
left(area.left(), true);
}
}
/* Returns true if point is inside the box, false otherwise. Includes the edges, so points equal to the edges
* will return true. */
bool Box::collide(const glm::vec2& point) const
{
return point.x >= left() && point.x <= right() && point.y >= top() && point.y <= bottom();
}
/* Returns true if the line segment intersects the box, false otherwise. If intersection 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. */
bool Box::collide(const Segment& segment, glm::vec2* intersection) const
{
/* do the faster check of whether the square represented by the segment diagonal collides to determine
* if we should 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;
}
}
/* Do segment collision with intersection specified by reference rather than pointer */
bool Box::collide(const Segment& segment, glm::vec2& intersection) const
{
return collide(segment, &intersection);
}
/* Return true if box collides with the passed box, false otherwise. If overlap is passed, it is set to
* the box representing the area where the two boxes overlap. */
bool Box::collide(const Box& box, Box* overlap) const
{
float top = std::max(this->top(), box.top());
float right = std::min(this->right(), box.right());
float bottom = std::min(this->bottom(), box.bottom());
float left = std::max(this->left(), box.left());
float w = right - left;
float h = bottom - top;
bool collide = w > 0 && h > 0;
if (collide && overlap != nullptr)
{
overlap->left(left);
overlap->top(top);
overlap->width(w);
overlap->height(h);
}
return collide;
}
/* Do a box to box collision test with overlap passed by reference instead of pointer */
bool Box::collide(const Box& box, Box& overlap) const
{
return collide(box, &overlap);
}
/* 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();
}
/* Feed a string representation of the box to the passed ostream */
std::ostream& operator<< (std::ostream& out, const Box& box)
{
out << box.string();
return out;
}