#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(left()), static_cast(top()), static_cast(width()), static_cast(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; }