#ifndef STAR_RECT_HPP #define STAR_RECT_HPP #include "StarLine.hpp" #include "StarList.hpp" namespace Star { // Axis aligned box that can be used as a bounding volume. template class Box { public: typedef Vector Coord; typedef Star::Line Line; typedef typename Line::IntersectResult LineIntersectResult; template using Enable2D = typename std::enable_if

::type; struct IntersectResult { // Whether or not the two objects intersect bool intersects; // How much *this* box must be moved in order to make them not intersect // anymore Coord overlap; // Whether or not the intersection is touching only. No overlap. bool glances; }; static Box null(); static Box inf(); // Returns an integral aligned box that at least contains the given floating // point box. template static Box integral(Box2 const& box); // Returns an integral aligned box that is equal to the given box rounded to // the nearest whole number (does not necessarily contain the given box). template static Box round(Box2 const& box); template static Box boundBoxOf(TN const&... list); template static Box boundBoxOfPoints(Collection const& collection); static Box withSize(Coord const& min, Coord const& size); static Box withCenter(Coord const& center, Coord const& size); Box(); Box(Coord const& min, Coord const& max); Box(Box const& b); Box& operator=(Box const& b); template explicit Box(Box const& b); // Is equal to null() bool isNull() const; // One or more dimensions are of negative magnitude bool isNegative() const; // One or more dimensions are of zero or negative magnitude bool isEmpty() const; // Sets the bounding box equal to one containing the given bounding box and // the current one. void combine(Box const& box); Box combined(Box const& box) const; // Sets the bounding box equal to one containing the current bounding box and // the given point. void combine(Coord const& point); Box combined(Coord const& point) const; // Sets the bounding box equal to the intersection of this one and the given // one. If there is no intersection than the box becomes negative in that // dimension. void limit(Box const& box); Box limited(Box const& box); // If any range has a min < max, swap them to make it non-null. void makePositive(); // Sets any empty (or negative) dimensions in the bounding box to the // corresponding range in the given bounding box. If the bounding box is not // empty in any dimension, then this has no effect. void rangeSetIfEmpty(Box const& b); Coord size() const; T size(size_t dim) const; // Sets bound box to the minimum bound box necessary to both have the given // aspect ratio and contain the current bounding box. void setAspect(Coord as, bool shrink = false); void makeCube(); Coord center() const; void setCenter(Coord const& c); void translate(Coord const& c); Box translated(Coord const& c) const; // Translate the Box the minimum distance so that it includes the given point void translateToInclude(Coord const& coord, Coord const& padding = Coord()); Vector range(size_t dim) const; void setRange(size_t dim, Vector v); void combineRange(size_t dim, Vector v); void limitRange(size_t dim, Vector v); // Expand from center. void expand(T factor); Box expanded(T factor) const; // Expand from center. void expand(Coord const& factor); Box expanded(Vector const& factor) const; // Scale around origin. void scale(T factor); Box scaled(T factor) const; // Scale around origin. void scale(Coord const& factor); Box scaled(Vector const& factor) const; // Increase all dimensions by a constant amount on all sides void pad(T amount); Box padded(T amount) const; // Increase all dimensions by a constant amount void pad(Coord const& amount); Box padded(Vector const& amount) const; // Opposite of pad void trim(T amount); Box trimmed(T amount) const; // Opposite of pad void trim(Coord const& amount); Box trimmed(Vector const& amount) const; // Flip around some dimension (may make box have negative volume) void flip(size_t dimension); Box flipped(size_t dimension) const; Coord const& min() const; Coord const& max() const; Coord& min(); Coord& max(); void setMin(Coord const& c); void setMax(Coord const& c); T volume() const; Box overlap(Box const& b) const; IntersectResult intersection(Box const& b) const; bool intersects(Box const& b, bool includeEdges = true) const; bool contains(Coord const& p, bool includeEdges = true) const; bool contains(Box const& b, bool includeEdges = true) const; // A version of contains that includes the min edges but not the max edges, // useful to select based on adjoining boxes without overlap. bool belongs(Coord const& p) const; bool containsEpsilon(Coord const& p, unsigned epsilons = 2) const; bool containsEpsilon(Box const& b, unsigned epsilons = 2) const; bool operator==(Box const& ref) const; bool operator!=(Box const& ref) const; // Find Coord inside box nearest to Coord nearestCoordTo(Coord const& c) const; // Find the coord in normalized space for this rect, so that 0 is the minimum // and 1 is the maximum. Coord normal(Coord const& coord) const; // The invers of normal, find the real space position of this normalized // coordinate. Coord eval(Coord const& normalizedCoord) const; // 2D Only // Slightly different to make ctor work template > Box(T minx, T miny, T maxx, T maxy); template Enable2D xMin() const; template Enable2D xMax() const; template Enable2D yMin() const; template Enable2D yMax() const; template Enable2D

setXMin(T xMin); template Enable2D

setXMax(T xMax); template Enable2D

setYMin(T yMin); template Enable2D

setYMax(T yMax); template Enable2D width() const; template Enable2D height() const; template Enable2D translate(T x, T y); template Enable2D translateToInclude(T x, T y, T xPadding = 0, T yPadding = 0); template Enable2D scale(T x, T y); template Enable2D expand(T x, T y); template Enable2D flipHorizontal(); template Enable2D flipVertical(); template Enable2D> edges() const; template Enable2D intersects(Line const& l) const; template Enable2D intersectsCircle(Coord const& position, T radius) const; template Enable2D edgeIntersection(Line const& l) const; // Returns a list of areas that are in this rect but not in the given rect. // Extra Credit: Implement this method for arbitrary dimensions. template Enable2D> subtract(Box const& rect) const; protected: template static void combineThings(Box& b, Coord const& point, TN const&... rest); template static void combineThings(Box& b, Box const& box, TN const&... rest); template static void combineThings(Box& b); Coord m_min; Coord m_max; }; template std::ostream& operator<<(std::ostream& os, Box const& box); template using Rect = Box; typedef Rect RectI; typedef Rect RectU; typedef Rect RectF; typedef Rect RectD; template Box Box::null() { return Box(Coord::filled(std::numeric_limits::max()), Coord::filled(std::numeric_limits::lowest())); } template Box Box::inf() { return Box(Coord::filled(std::numeric_limits::lowest()), Coord::filled(std::numeric_limits::max())); } template template Box Box::integral(Box2 const& box) { return Box(Coord::floor(box.min()), Coord::ceil(box.max())); } template template Box Box::round(Box2 const& box) { return Box(Coord::round(box.min()), Coord::round(box.max())); } template template Box Box::boundBoxOf(TN const&... list) { Box b = null(); combineThings(b, list...); return b; } template template Box Box::boundBoxOfPoints(Collection const& collection) { Box b = null(); for (auto const& point : collection) b.combine(Coord(point)); return b; } template Box Box::withSize(Coord const& min, Coord const& size) { return Box(min, min + size); } template Box Box::withCenter(Coord const& center, Coord const& size) { return Box(center - size / 2, center + size / 2); } template Box::Box() {} template Box::Box(Coord const& min, Coord const& max) : m_min(min), m_max(max) {} template Box::Box(Box const& b) : m_min(b.min()), m_max(b.max()) {} template Box& Box::operator=(Box const& b) { m_min = b.m_min; m_max = b.m_max; return *this; } template template Box::Box(Box const& b) : m_min(b.min()), m_max(b.max()) {} template bool Box::isNull() const { return m_min == Coord::filled(std::numeric_limits::max()) && m_max == Coord::filled(std::numeric_limits::lowest()); } template bool Box::isNegative() const { for (size_t i = 0; i < N; ++i) { if (m_max[i] < m_min[i]) return true; } return false; } template bool Box::isEmpty() const { for (size_t i = 0; i < N; ++i) { if (m_max[i] <= m_min[i]) return true; } return false; } template void Box::combine(Box const& box) { m_min = box.m_min.piecewiseMin(m_min); m_max = box.m_max.piecewiseMax(m_max); } template Box Box::combined(Box const& box) const { auto b = *this; b.combine(box); return b; } template void Box::combine(Coord const& point) { m_min = m_min.piecewiseMin(point); m_max = m_max.piecewiseMax(point); } template Box Box::combined(Coord const& point) const { auto b = *this; b.combine(point); return b; } template void Box::limit(Box const& box) { m_min = m_min.piecewiseMax(box.m_min); m_max = m_max.piecewiseMin(box.m_max); } template Box Box::limited(Box const& box) { auto b = *this; b.limit(box); return b; } template void Box::makePositive() { for (size_t i = 0; i < N; ++i) { if (m_max[i] < m_min[i]) std::swap(m_max[i], m_min[i]); } } template void Box::rangeSetIfEmpty(Box const& b) { for (size_t i = 0; i < N; ++i) { if (m_max[i] <= m_min[i]) setRange(i, b.range(i)); } } template void Box::makeCube() { setAspect(Coord::filled(1)); } template auto Box::size() const -> Coord { return m_max - m_min; } template T Box::size(size_t dim) const { return m_max[dim] - m_min[dim]; } template void Box::setAspect(Coord as, bool shrink) { Coord nBox = (m_max - m_min).piecewiseDivide(as); Coord extent; if (shrink) extent = Coord::filled(nBox.min()); else extent = Coord::filled(nBox.max()); extent = extent.piecewiseMult(as); Coord center = (m_max + m_min) / 2; m_max = center + extent / 2; m_min = center - extent / 2; } template auto Box::center() const -> Coord { return (m_min + m_max) / 2; } template void Box::setCenter(Coord const& c) { translate(c - center()); } template void Box::translate(Coord const& c) { m_min += c; m_max += c; } template Box Box::translated(Coord const& c) const { auto b = *this; b.translate(c); return b; } template void Box::translateToInclude(Coord const& coord, Coord const& padding) { Coord translation; for (size_t i = 0; i < N; ++i) { if (coord[i] < m_min[i] + padding[i]) translation[i] = coord[i] - m_min[i] - padding[i]; else if (coord[i] > m_max[i] - padding[i]) translation[i] = coord[i] - m_max[i] + padding[i]; } translate(translation); } template Vector Box::range(size_t dim) const { return Coord(m_min[dim], m_max[dim]); } template void Box::setRange(size_t dim, Vector v) { m_min[dim] = v[0]; m_max[dim] = v[1]; } template void Box::combineRange(size_t dim, Vector v) { m_min[dim] = std::min(m_min[dim], v[0]); m_max[dim] = std::max(m_max[dim], v[1]); } template void Box::limitRange(size_t dim, Vector v) { m_min[dim] = std::max(m_min[dim], v[0]); m_max[dim] = std::min(m_max[dim], v[1]); } template void Box::expand(T factor) { for (size_t i = 0; i < N; ++i) { auto rng = range(i); T center = rng.sum() / 2; T newDist = (rng[1] - rng[0]) * factor; setRange(i, Coord(center - newDist / 2, center + newDist / 2)); } } template Box Box::expanded(T factor) const { auto b = *this; b.expand(factor); return b; } template void Box::expand(Coord const& factor) { for (size_t i = 0; i < N; ++i) { auto rng = range(i); T center = rng.sum() / 2; T newDist = (rng[1] - rng[0]) * factor[i]; setRange(i, Coord(center - newDist / 2, center + newDist / 2)); } } template Box Box::expanded(Coord const& factor) const { auto b = *this; b.expand(factor); return b; } template void Box::scale(T factor) { for (size_t i = 0; i < N; ++i) setRange(i, range(i) * factor); } template Box Box::scaled(T factor) const { auto b = *this; b.scale(factor); return b; } template void Box::scale(Coord const& factor) { for (size_t i = 0; i < N; ++i) setRange(i, range(i) * factor[i]); } template Box Box::scaled(Coord const& factor) const { auto b = *this; b.scale(factor); return b; } template void Box::pad(T amount) { for (size_t i = 0; i < N; ++i) { m_min[i] -= amount; m_max[i] += amount; } } template Box Box::padded(T amount) const { auto b = *this; b.pad(amount); return b; } template void Box::pad(Coord const& amount) { for (size_t i = 0; i < N; ++i) { m_min[i] -= amount[i]; m_max[i] += amount[i]; } } template Box Box::padded(Coord const& amount) const { auto b = *this; b.pad(amount); return b; } template void Box::trim(T amount) { pad(-amount); } template Box Box::trimmed(T amount) const { auto b = *this; b.trim(amount); return b; } template void Box::trim(Coord const& amount) { pad(-amount); } template Box Box::trimmed(Coord const& amount) const { auto b = *this; b.trim(amount); return b; } template void Box::flip(size_t dimension) { std::swap(m_min[dimension], m_max[dimension]); } template Box Box::flipped(size_t dimension) const { auto b = *this; b.flip(dimension); return b; } template auto Box::normal(Coord const& coord) const -> Coord { return (coord - m_min).piecewiseDivide(m_max - m_min); } template auto Box::eval(Coord const& normalizedCoord) const -> Coord { return normalizedCoord.piecewiseMultiply(m_max - m_min) + m_min; } template auto Box::min() const -> Coord const & { return m_min; } template auto Box::max() const -> Coord const & { return m_max; } template auto Box::min() -> Coord & { return m_min; } template auto Box::max() -> Coord & { return m_max; } template void Box::setMin(Coord const& c) { m_min = c; } template void Box::setMax(Coord const& c) { m_max = c; } template T Box::volume() const { return size().product(); } template auto Box::overlap(Box const& b) const -> Box { Box result = *this; for (size_t i = 0; i < N; ++i) { result.m_min[i] = std::max(result.m_min[i], b.m_min[i]); result.m_max[i] = std::min(result.m_max[i], b.m_max[i]); } return result; } template auto Box::intersection(Box const& b) const -> IntersectResult { IntersectResult res; T overlap = std::numeric_limits::max(); size_t dim = 0; bool negative = false; for (size_t i = 0; i < N; ++i) { if (m_max[i] - b.m_min[i] < overlap) { overlap = m_max[i] - b.m_min[i]; dim = i; negative = true; } if (b.m_max[i] - m_min[i] < overlap) { overlap = b.m_max[i] - m_min[i]; dim = i; negative = false; } } res.overlap = Coord(); if (overlap > 0) { res.intersects = true; res.overlap[dim] = overlap; } else { res.intersects = false; res.overlap[dim] = -overlap; } if (negative) res.overlap = -res.overlap; if (res.overlap == Coord()) { res.glances = true; } else { res.glances = false; } return res; } template bool Box::intersects(Box const& b, bool includeEdges) const { for (size_t i = 0; i < N; ++i) { if (includeEdges) { if (m_max[i] < b.m_min[i] || b.m_max[i] < m_min[i]) return false; } else { if (m_max[i] <= b.m_min[i] || b.m_max[i] <= m_min[i]) return false; } } return true; } template bool Box::contains(Coord const& p, bool includeEdges) const { for (size_t i = 0; i < N; ++i) { if (includeEdges) { if (p[i] < m_min[i] || p[i] > m_max[i]) return false; } else { if (p[i] <= m_min[i] || p[i] >= m_max[i]) return false; } } return true; } template bool Box::contains(Box const& b, bool includeEdges) const { return contains(b.min(), includeEdges) && contains(b.max(), includeEdges); } template bool Box::belongs(Coord const& p) const { for (size_t i = 0; i < N; ++i) { if (p[i] < m_min[i] || p[i] >= m_max[i]) return false; } return true; } template bool Box::containsEpsilon(Coord const& p, unsigned epsilons) const { for (size_t i = 0; i < N; ++i) { if (p[i] < m_min[i] || p[i] > m_max[i]) return false; if (nearEqual(p[i], m_min[i], epsilons) || nearEqual(p[i], m_max[i], epsilons)) return false; } return true; } template bool Box::containsEpsilon(Box const& b, unsigned epsilons) const { return containsEpsilon(b.min(), epsilons) && containsEpsilon(b.max(), epsilons); } template bool Box::operator==(Box const& ref) const { return m_min == ref.m_min && m_max == ref.m_max; } template bool Box::operator!=(Box const& ref) const { return m_min != ref.m_min || m_max != ref.m_max; } template template void Box::combineThings(Box& b, Coord const& point, TN const&... rest) { b.combine(point); combineThings(b, rest...); } template template void Box::combineThings(Box& b, Box const& box, TN const&... rest) { b.combine(box); combineThings(b, rest...); } template template void Box::combineThings(Box&) {} template std::ostream& operator<<(std::ostream& os, Box const& box) { os << "Box{min:" << box.min() << " max:" << box.max() << "}"; return os; } template template Box::Box(T minx, T miny, T maxx, T maxy) : Box(Coord(minx, miny), Coord(maxx, maxy)) {} template template auto Box::xMin() const -> Enable2D { return min()[0]; } template template auto Box::xMax() const -> Enable2D { return max()[0]; } template template auto Box::yMin() const -> Enable2D { return min()[1]; } template template auto Box::yMax() const -> Enable2D { return max()[1]; } template template auto Box::setXMin(T xMin) -> Enable2D

{ m_min[0] = xMin; } template template auto Box::setXMax(T xMax) -> Enable2D

{ m_max[0] = xMax; } template template auto Box::setYMin(T yMin) -> Enable2D

{ m_min[1] = yMin; } template template auto Box::setYMax(T yMax) -> Enable2D

{ m_max[1] = yMax; } template template auto Box::width() const -> Enable2D { return size(0); } template template auto Box::height() const -> Enable2D { return size(1); } template template auto Box::translate(T x, T y) -> Enable2D { translate(Coord(x, y)); } template template auto Box::translateToInclude(T x, T y, T xPadding, T yPadding) -> Enable2D { translateToInclude(Coord(x, y), Coord(xPadding, yPadding)); } template template auto Box::scale(T x, T y) -> Enable2D { scale(Coord(x, y)); } template template auto Box::expand(T x, T y) -> Enable2D { expand(Coord(x, y)); } template template auto Box::flipHorizontal() -> Enable2D { flip(0); } template template auto Box::flipVertical() -> Enable2D { flip(1); } template template auto Box::edges() const -> Enable2D> { Array res; res[0] = {min(), {min()[0], max()[1]}}; res[1] = {min(), {max()[0], min()[1]}}; res[2] = {{min()[0], max()[1]}, max()}; res[3] = {{max()[0], min()[1]}, max()}; return res; } template template auto Box::intersects(Line const& l) const -> Enable2D { if (contains(l.min()) || contains(l.max())) return true; for (auto i : edges()) { if (l.intersects(i)) return true; } return false; } template template auto Box::intersectsCircle(Coord const& position, T radius) const -> Enable2D { if (contains(position)) return true; for (auto const& e : edges()) { if (e.distanceTo(position) <= radius) return true; } return false; } // returns the closest intersection point (from l.min()) template template auto Box::edgeIntersection(Line const& l) const -> Enable2D { Array candidates; size_t numCandidates = 0; for (auto i : edges()) { auto res = l.intersection(i); if (res.intersects) candidates[numCandidates++] = res; } // How glancing is determined // There are a few possibilities // if candidates is empty then no intersection, easy // if there is only one candidate then there are two possibilities, glancing // or not // But! if an endpoint is inside the rect, not just on the edge then it's // false // if there are two candidates and at least one of them is not glancing then // false // if there are two candidates and at they're both glancing then there's a few // possibilities // first, the line cuts through the corner, we can detect this by seeing if // the point is in the // box but not on the edge // second, the line cuts across the corner, this case is true // third, the line coincides with one of the sides, this case is also true. // if there are 3 candidates then two cases // first, the line coincides with one of the sides, and glances off of the // other two, true // second, the line cuts through a corner and reaches the far side, false // we can tell these apart by determining if any intersections coincide // if there are 4 candidates then the only possible case is false (cutting // through both corners if (numCandidates != 0) { std::sort(candidates.ptr(), candidates.ptr() + numCandidates, [&](LineIntersectResult const& a, LineIntersectResult const& b) { return a.t < b.t; }); if (numCandidates == 1) { if (contains(l.min(), false) || contains(l.max(), false)) { candidates[0].glances = false; } } else if (numCandidates == 2) { if (contains(l.min(), false) || contains(l.max(), false)) { candidates[0].glances = false; } else if (contains(l.min()) && !candidates[1].glances) { candidates[0].glances = false; } if (candidates[1].coincides) { // If we coincide on either consider it true candidates[0].coincides = true; } } else if (numCandidates == 3) { if (candidates[0].coincides || candidates[1].coincides || candidates[2].coincides) { candidates[0].glances = true; candidates[0].coincides = true; } else { candidates[0].glances = false; } } else { candidates[0].glances = false; candidates[0].coincides = false; } return candidates[0]; } else { return LineIntersectResult(); } } template template auto Box::subtract(Box const& rect) const -> Enable2D> { List regions; auto overlap = Box::overlap(rect); if (overlap.isEmpty()) { // If this rect doesn't overlap at all with the subtracted one, obviously // the entire rect is new territory. regions.append(*this); } else { // If there is overlap with this rect, we need to add the left, bottom, // right, and top sections. These can overlap at the corners, so the left // and right sections will take the lower / upper left and lower / upper // right corners, and the top and bottom will be limited to the width of // the overlap section. if (xMin() < overlap.xMin()) regions.append(Box(xMin(), yMin(), overlap.xMin(), yMax())); if (overlap.xMax() < xMax()) regions.append(Box(overlap.xMax(), yMin(), xMax(), yMax())); if (yMin() < overlap.yMin()) regions.append(Box(rect.xMin(), yMin(), rect.xMax(), overlap.yMin())); if (overlap.yMax() < yMax()) regions.append(Box(rect.xMin(), overlap.yMax(), rect.xMax(), yMax())); } return regions; } template auto Box::nearestCoordTo(Coord const& c) const -> Coord { Coord result = c; for (size_t i = 0; i < N; ++i) result[i] = clamp(result[i], m_min[i], m_max[i]); return result; } } #endif