diff options
author | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
---|---|---|
committer | Kae <80987908+Novaenia@users.noreply.github.com> | 2023-06-20 14:33:09 +1000 |
commit | 6352e8e3196f78388b6c771073f9e03eaa612673 (patch) | |
tree | e23772f79a7fbc41bc9108951e9e136857484bf4 /source/core/StarRect.hpp | |
parent | 6741a057e5639280d85d0f88ba26f000baa58f61 (diff) |
everything everywhere
all at once
Diffstat (limited to 'source/core/StarRect.hpp')
-rw-r--r-- | source/core/StarRect.hpp | 1068 |
1 files changed, 1068 insertions, 0 deletions
diff --git a/source/core/StarRect.hpp b/source/core/StarRect.hpp new file mode 100644 index 0000000..38bbabc --- /dev/null +++ b/source/core/StarRect.hpp @@ -0,0 +1,1068 @@ +#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 <typename T, size_t N> +class Box { +public: + typedef Vector<T, N> Coord; + typedef Star::Line<T, N> Line; + typedef typename Line::IntersectResult LineIntersectResult; + + template <size_t P, typename T2 = void> + using Enable2D = typename std::enable_if<P == 2 && N == P, T2>::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 <typename Box2> + 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 <typename Box2> + static Box round(Box2 const& box); + + template <typename... TN> + static Box boundBoxOf(TN const&... list); + + template <typename Collection> + 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); + + template <typename T2> + explicit Box(Box<T2, N> 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<T, 2> range(size_t dim) const; + void setRange(size_t dim, Vector<T, 2> v); + void combineRange(size_t dim, Vector<T, 2> v); + void limitRange(size_t dim, Vector<T, 2> v); + + // Expand from center. + void expand(T factor); + Box expanded(T factor) const; + + // Expand from center. + void expand(Coord const& factor); + Box expanded(Vector<T, N> 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<T, N> 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<T, N> 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<T, N> 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 <size_t P = N, class = Enable2D<P>> + Box(T minx, T miny, T maxx, T maxy); + + template <size_t P = N> + Enable2D<P, T> xMin() const; + template <size_t P = N> + Enable2D<P, T> xMax() const; + template <size_t P = N> + Enable2D<P, T> yMin() const; + template <size_t P = N> + Enable2D<P, T> yMax() const; + + template <size_t P = N> + Enable2D<P> setXMin(T xMin); + template <size_t P = N> + Enable2D<P> setXMax(T xMax); + template <size_t P = N> + Enable2D<P> setYMin(T yMin); + template <size_t P = N> + Enable2D<P> setYMax(T yMax); + + template <size_t P = N> + Enable2D<P, T> width() const; + template <size_t P = N> + Enable2D<P, T> height() const; + + template <size_t P = N> + Enable2D<P, void> translate(T x, T y); + template <size_t P = N> + Enable2D<P, void> translateToInclude(T x, T y, T xPadding = 0, T yPadding = 0); + template <size_t P = N> + Enable2D<P, void> scale(T x, T y); + template <size_t P = N> + Enable2D<P, void> expand(T x, T y); + template <size_t P = N> + Enable2D<P, void> flipHorizontal(); + template <size_t P = N> + Enable2D<P, void> flipVertical(); + + template <size_t P = N> + Enable2D<P, Array<Line, 4>> edges() const; + template <size_t P = N> + Enable2D<P, bool> intersects(Line const& l) const; + template <size_t P = N> + Enable2D<P, bool> intersectsCircle(Coord const& position, T radius) const; + template <size_t P = N> + Enable2D<P, LineIntersectResult> 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 <size_t P = N> + Enable2D<P, List<Box>> subtract(Box const& rect) const; + +protected: + template <typename... TN> + static void combineThings(Box& b, Coord const& point, TN const&... rest); + + template <typename... TN> + static void combineThings(Box& b, Box const& box, TN const&... rest); + + template <typename... TN> + static void combineThings(Box& b); + + Coord m_min; + Coord m_max; +}; + +template <typename T, size_t N> +std::ostream& operator<<(std::ostream& os, Box<T, N> const& box); + +template<typename T> +using Rect = Box<T, 2>; + +typedef Rect<int> RectI; +typedef Rect<unsigned> RectU; +typedef Rect<float> RectF; +typedef Rect<double> RectD; + +template <typename T, size_t N> +Box<T, N> Box<T, N>::null() { + return Box(Coord::filled(std::numeric_limits<T>::max()), Coord::filled(std::numeric_limits<T>::lowest())); +} + +template <typename T, size_t N> +Box<T, N> Box<T, N>::inf() { + return Box(Coord::filled(std::numeric_limits<T>::lowest()), Coord::filled(std::numeric_limits<T>::max())); +} + +template <typename T, size_t N> +template <typename Box2> +Box<T, N> Box<T, N>::integral(Box2 const& box) { + return Box(Coord::floor(box.min()), Coord::ceil(box.max())); +} + +template <typename T, size_t N> +template <typename Box2> +Box<T, N> Box<T, N>::round(Box2 const& box) { + return Box(Coord::round(box.min()), Coord::round(box.max())); +} + +template <typename T, size_t N> +template <typename... TN> +Box<T, N> Box<T, N>::boundBoxOf(TN const&... list) { + Box b = null(); + combineThings(b, list...); + return b; +} + +template <typename T, size_t N> +template <typename Collection> +Box<T, N> Box<T, N>::boundBoxOfPoints(Collection const& collection) { + Box b = null(); + for (auto const& point : collection) + b.combine(Coord(point)); + return b; +} + +template <typename T, size_t N> +Box<T, N> Box<T, N>::withSize(Coord const& min, Coord const& size) { + return Box(min, min + size); +} + +template <typename T, size_t N> +Box<T, N> Box<T, N>::withCenter(Coord const& center, Coord const& size) { + return Box(center - size / 2, center + size / 2); +} + +template <typename T, size_t N> +Box<T, N>::Box() {} + +template <typename T, size_t N> +Box<T, N>::Box(Coord const& min, Coord const& max) + : m_min(min), m_max(max) {} + +template <typename T, size_t N> +Box<T, N>::Box(Box const& b) + : m_min(b.min()), m_max(b.max()) {} + +template <typename T, size_t N> +template <typename T2> +Box<T, N>::Box(Box<T2, N> const& b) + : m_min(b.min()), m_max(b.max()) {} + +template <typename T, size_t N> +bool Box<T, N>::isNull() const { + return m_min == Coord::filled(std::numeric_limits<T>::max()) + && m_max == Coord::filled(std::numeric_limits<T>::lowest()); +} + +template <typename T, size_t N> +bool Box<T, N>::isNegative() const { + for (size_t i = 0; i < N; ++i) { + if (m_max[i] < m_min[i]) + return true; + } + return false; +} + +template <typename T, size_t N> +bool Box<T, N>::isEmpty() const { + for (size_t i = 0; i < N; ++i) { + if (m_max[i] <= m_min[i]) + return true; + } + return false; +} + +template <typename T, size_t N> +void Box<T, N>::combine(Box const& box) { + m_min = box.m_min.piecewiseMin(m_min); + m_max = box.m_max.piecewiseMax(m_max); +} + +template <typename T, size_t N> +Box<T, N> Box<T, N>::combined(Box const& box) const { + auto b = *this; + b.combine(box); + return b; +} + +template <typename T, size_t N> +void Box<T, N>::combine(Coord const& point) { + m_min = m_min.piecewiseMin(point); + m_max = m_max.piecewiseMax(point); +} + +template <typename T, size_t N> +Box<T, N> Box<T, N>::combined(Coord const& point) const { + auto b = *this; + b.combine(point); + return b; +} + +template <typename T, size_t N> +void Box<T, N>::limit(Box const& box) { + m_min = m_min.piecewiseMax(box.m_min); + m_max = m_max.piecewiseMin(box.m_max); +} + +template <typename T, size_t N> +Box<T, N> Box<T, N>::limited(Box const& box) { + auto b = *this; + b.limit(box); + return b; +} + +template <typename T, size_t N> +void Box<T, N>::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 <typename T, size_t N> +void Box<T, N>::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 <typename T, size_t N> +void Box<T, N>::makeCube() { + setAspect(Coord::filled(1)); +} + +template <typename T, size_t N> +auto Box<T, N>::size() const -> Coord { + return m_max - m_min; +} + +template <typename T, size_t N> +T Box<T, N>::size(size_t dim) const { + return m_max[dim] - m_min[dim]; +} + +template <typename T, size_t N> +void Box<T, N>::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 <typename T, size_t N> +auto Box<T, N>::center() const -> Coord { + return (m_min + m_max) / 2; +} + +template <typename T, size_t N> +void Box<T, N>::setCenter(Coord const& c) { + translate(c - center()); +} + +template <typename T, size_t N> +void Box<T, N>::translate(Coord const& c) { + m_min += c; + m_max += c; +} + +template <typename T, size_t N> +Box<T, N> Box<T, N>::translated(Coord const& c) const { + auto b = *this; + b.translate(c); + return b; +} + +template <typename T, size_t N> +void Box<T, N>::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 <typename T, size_t N> +Vector<T, 2> Box<T, N>::range(size_t dim) const { + return Coord(m_min[dim], m_max[dim]); +} + +template <typename T, size_t N> +void Box<T, N>::setRange(size_t dim, Vector<T, 2> v) { + m_min[dim] = v[0]; + m_max[dim] = v[1]; +} + +template <typename T, size_t N> +void Box<T, N>::combineRange(size_t dim, Vector<T, 2> v) { + m_min[dim] = std::min(m_min[dim], v[0]); + m_max[dim] = std::max(m_max[dim], v[1]); +} + +template <typename T, size_t N> +void Box<T, N>::limitRange(size_t dim, Vector<T, 2> v) { + m_min[dim] = std::max(m_min[dim], v[0]); + m_max[dim] = std::min(m_max[dim], v[1]); +} + +template <typename T, size_t N> +void Box<T, N>::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 <typename T, size_t N> +Box<T, N> Box<T, N>::expanded(T factor) const { + auto b = *this; + b.expand(factor); + return b; +} + +template <typename T, size_t N> +void Box<T, N>::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 <typename T, size_t N> +Box<T, N> Box<T, N>::expanded(Coord const& factor) const { + auto b = *this; + b.expand(factor); + return b; +} + +template <typename T, size_t N> +void Box<T, N>::scale(T factor) { + for (size_t i = 0; i < N; ++i) + setRange(i, range(i) * factor); +} + +template <typename T, size_t N> +Box<T, N> Box<T, N>::scaled(T factor) const { + auto b = *this; + b.scale(factor); + return b; +} + +template <typename T, size_t N> +void Box<T, N>::scale(Coord const& factor) { + for (size_t i = 0; i < N; ++i) + setRange(i, range(i) * factor[i]); +} + +template <typename T, size_t N> +Box<T, N> Box<T, N>::scaled(Coord const& factor) const { + auto b = *this; + b.scale(factor); + return b; +} + +template <typename T, size_t N> +void Box<T, N>::pad(T amount) { + for (size_t i = 0; i < N; ++i) { + m_min[i] -= amount; + m_max[i] += amount; + } +} + +template <typename T, size_t N> +Box<T, N> Box<T, N>::padded(T amount) const { + auto b = *this; + b.pad(amount); + return b; +} + +template <typename T, size_t N> +void Box<T, N>::pad(Coord const& amount) { + for (size_t i = 0; i < N; ++i) { + m_min[i] -= amount[i]; + m_max[i] += amount[i]; + } +} + +template <typename T, size_t N> +Box<T, N> Box<T, N>::padded(Coord const& amount) const { + auto b = *this; + b.pad(amount); + return b; +} + +template <typename T, size_t N> +void Box<T, N>::trim(T amount) { + pad(-amount); +} + +template <typename T, size_t N> +Box<T, N> Box<T, N>::trimmed(T amount) const { + auto b = *this; + b.trim(amount); + return b; +} + +template <typename T, size_t N> +void Box<T, N>::trim(Coord const& amount) { + pad(-amount); +} + +template <typename T, size_t N> +Box<T, N> Box<T, N>::trimmed(Coord const& amount) const { + auto b = *this; + b.trim(amount); + return b; +} + +template <typename T, size_t N> +void Box<T, N>::flip(size_t dimension) { + std::swap(m_min[dimension], m_max[dimension]); +} + +template <typename T, size_t N> +Box<T, N> Box<T, N>::flipped(size_t dimension) const { + auto b = *this; + b.flip(dimension); + return b; +} + +template <typename T, size_t N> +auto Box<T, N>::normal(Coord const& coord) const -> Coord { + return (coord - m_min).piecewiseDivide(m_max - m_min); +} + +template <typename T, size_t N> +auto Box<T, N>::eval(Coord const& normalizedCoord) const -> Coord { + return normalizedCoord.piecewiseMultiply(m_max - m_min) + m_min; +} + +template <typename T, size_t N> +auto Box<T, N>::min() const -> Coord const & { + return m_min; +} + +template <typename T, size_t N> +auto Box<T, N>::max() const -> Coord const & { + return m_max; +} + +template <typename T, size_t N> +auto Box<T, N>::min() -> Coord & { + return m_min; +} + +template <typename T, size_t N> +auto Box<T, N>::max() -> Coord & { + return m_max; +} + +template <typename T, size_t N> +void Box<T, N>::setMin(Coord const& c) { + m_min = c; +} + +template <typename T, size_t N> +void Box<T, N>::setMax(Coord const& c) { + m_max = c; +} + +template <typename T, size_t N> +T Box<T, N>::volume() const { + return size().product(); +} + +template <typename T, size_t N> +auto Box<T, N>::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 <typename T, size_t N> +auto Box<T, N>::intersection(Box const& b) const -> IntersectResult { + IntersectResult res; + + T overlap = std::numeric_limits<T>::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 <typename T, size_t N> +bool Box<T, N>::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 <typename T, size_t N> +bool Box<T, N>::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 <typename T, size_t N> +bool Box<T, N>::contains(Box const& b, bool includeEdges) const { + return contains(b.min(), includeEdges) && contains(b.max(), includeEdges); +} + +template <typename T, size_t N> +bool Box<T, N>::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 <typename T, size_t N> +bool Box<T, N>::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 <typename T, size_t N> +bool Box<T, N>::containsEpsilon(Box const& b, unsigned epsilons) const { + return containsEpsilon(b.min(), epsilons) && containsEpsilon(b.max(), epsilons); +} + +template <typename T, size_t N> +bool Box<T, N>::operator==(Box const& ref) const { + return m_min == ref.m_min && m_max == ref.m_max; +} + +template <typename T, size_t N> +bool Box<T, N>::operator!=(Box const& ref) const { + return m_min != ref.m_min || m_max != ref.m_max; +} + +template <typename T, size_t N> +template <typename... TN> +void Box<T, N>::combineThings(Box& b, Coord const& point, TN const&... rest) { + b.combine(point); + combineThings(b, rest...); +} + +template <typename T, size_t N> +template <typename... TN> +void Box<T, N>::combineThings(Box& b, Box const& box, TN const&... rest) { + b.combine(box); + combineThings(b, rest...); +} + +template <typename T, size_t N> +template <typename... TN> +void Box<T, N>::combineThings(Box&) {} + +template <typename T, size_t N> +std::ostream& operator<<(std::ostream& os, Box<T, N> const& box) { + os << "Box{min:" << box.min() << " max:" << box.max() << "}"; + return os; +} + +template <typename T, size_t N> +template <size_t P, class> +Box<T, N>::Box(T minx, T miny, T maxx, T maxy) + : Box(Coord(minx, miny), Coord(maxx, maxy)) {} + +template <typename T, size_t N> +template <size_t P> +auto Box<T, N>::xMin() const -> Enable2D<P, T> { + return min()[0]; +} + +template <typename T, size_t N> +template <size_t P> +auto Box<T, N>::xMax() const -> Enable2D<P, T> { + return max()[0]; +} + +template <typename T, size_t N> +template <size_t P> +auto Box<T, N>::yMin() const -> Enable2D<P, T> { + return min()[1]; +} + +template <typename T, size_t N> +template <size_t P> +auto Box<T, N>::yMax() const -> Enable2D<P, T> { + return max()[1]; +} + +template <typename T, size_t N> +template <size_t P> +auto Box<T, N>::setXMin(T xMin) -> Enable2D<P> { + m_min[0] = xMin; +} + +template <typename T, size_t N> +template <size_t P> +auto Box<T, N>::setXMax(T xMax) -> Enable2D<P> { + m_max[0] = xMax; +} + +template <typename T, size_t N> +template <size_t P> +auto Box<T, N>::setYMin(T yMin) -> Enable2D<P> { + m_min[1] = yMin; +} + +template <typename T, size_t N> +template <size_t P> +auto Box<T, N>::setYMax(T yMax) -> Enable2D<P> { + m_max[1] = yMax; +} + +template <typename T, size_t N> +template <size_t P> +auto Box<T, N>::width() const -> Enable2D<P, T> { + return size(0); +} + +template <typename T, size_t N> +template <size_t P> +auto Box<T, N>::height() const -> Enable2D<P, T> { + return size(1); +} + +template <typename T, size_t N> +template <size_t P> +auto Box<T, N>::translate(T x, T y) -> Enable2D<P, void> { + translate(Coord(x, y)); +} + +template <typename T, size_t N> +template <size_t P> +auto Box<T, N>::translateToInclude(T x, T y, T xPadding, T yPadding) -> Enable2D<P, void> { + translateToInclude(Coord(x, y), Coord(xPadding, yPadding)); +} + +template <typename T, size_t N> +template <size_t P> +auto Box<T, N>::scale(T x, T y) -> Enable2D<P, void> { + scale(Coord(x, y)); +} + +template <typename T, size_t N> +template <size_t P> +auto Box<T, N>::expand(T x, T y) -> Enable2D<P, void> { + expand(Coord(x, y)); +} + +template <typename T, size_t N> +template <size_t P> +auto Box<T, N>::flipHorizontal() -> Enable2D<P, void> { + flip(0); +} + +template <typename T, size_t N> +template <size_t P> +auto Box<T, N>::flipVertical() -> Enable2D<P, void> { + flip(1); +} + +template <typename T, size_t N> +template <size_t P> +auto Box<T, N>::edges() const -> Enable2D<P, Array<Line, 4>> { + Array<Line, 4> 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 <typename T, size_t N> +template <size_t P> +auto Box<T, N>::intersects(Line const& l) const -> Enable2D<P, bool> { + if (contains(l.min()) || contains(l.max())) + return true; + + for (auto i : edges()) { + if (l.intersects(i)) + return true; + } + return false; +} + +template <typename T, size_t N> +template <size_t P> +auto Box<T, N>::intersectsCircle(Coord const& position, T radius) const -> Enable2D<P, bool> { + 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 <typename T, size_t N> +template <size_t P> +auto Box<T, N>::edgeIntersection(Line const& l) const -> Enable2D<P, LineIntersectResult> { + Array<LineIntersectResult, 4> 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 <typename T, size_t N> +template <size_t P> +auto Box<T, N>::subtract(Box const& rect) const -> Enable2D<P, List<Box>> { + List<Box> 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 <typename T, size_t N> +auto Box<T, N>::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 |