diff options
Diffstat (limited to 'source/core/StarPoly.hpp')
-rw-r--r-- | source/core/StarPoly.hpp | 750 |
1 files changed, 750 insertions, 0 deletions
diff --git a/source/core/StarPoly.hpp b/source/core/StarPoly.hpp new file mode 100644 index 0000000..58cfca8 --- /dev/null +++ b/source/core/StarPoly.hpp @@ -0,0 +1,750 @@ +#ifndef STAR_POLY_HPP +#define STAR_POLY_HPP + +#include <numeric> + +#include "StarRect.hpp" + +namespace Star { + +template <typename DataType> +class Polygon { +public: + typedef Vector<DataType, 2> Vertex; + typedef Star::Line<DataType, 2> Line; + typedef Star::Box<DataType, 2> Rect; + + struct IntersectResult { + // Whether or not the two objects intersect + bool intersects; + // How much *this* poly must be moved in order to make them not intersect + // anymore + Vertex overlap; + }; + + struct LineIntersectResult { + // Point of intersection + Vertex point; + // t value at the point of intersection of the line that was checked + DataType along; + // Side that the line first intersected, if the line starts inside the + // polygon, this will not be set. + Maybe<size_t> intersectedSide; + }; + + typedef List<Vertex> VertexList; + typedef typename VertexList::iterator iterator; + typedef typename VertexList::const_iterator const_iterator; + + static Polygon convexHull(VertexList points); + static Polygon clip(Polygon inputPoly, Polygon convexClipPoly); + + // Creates a null polygon + Polygon(); + Polygon(Polygon const& rhs); + Polygon(Polygon&& rhs); + + template <typename DataType2> + explicit Polygon(Box<DataType2, 2> const& rect); + + template <typename DataType2> + explicit Polygon(Polygon<DataType2> const& p2); + + // This seems weird, but it isn't. SAT intersection works perfectly well + // with one Poly having only a single vertex. + explicit Polygon(Vertex const& coord); + + // When specifying a polygon using this constructor the list should be in + // counterclockwise order. + explicit Polygon(VertexList const& vertexes); + + Polygon(std::initializer_list<Vertex> vertexes); + + bool isNull() const; + + bool isConvex() const; + float convexArea() const; + + void deduplicateVertexes(float maxDistance); + + void add(Vertex const& a); + void remove(size_t i); + + void clear(); + + VertexList const& vertexes() const; + VertexList& vertexes(); + + size_t sides() const; + + Line side(size_t i) const; + + DataType distance(Vertex const& c) const; + + void translate(Vertex const& c); + + void setCenter(Vertex const& c); + + void rotate(DataType a, Vertex const& c = Vertex()); + + void scale(Vertex const& s, Vertex const& c = Vertex()); + void scale(DataType s, Vertex const& c = Vertex()); + + void flipHorizontal(DataType horizontalPos = DataType()); + void flipVertical(DataType verticalPos = DataType()); + + template <typename DataType2> + void transform(Matrix3<DataType2> const& transMat); + + Vertex const& operator[](size_t i) const; + Vertex& operator[](size_t i); + + bool operator==(Polygon const& rhs) const; + + Polygon& operator=(Polygon const& rhs); + Polygon& operator=(Polygon&& rhs); + + iterator begin(); + const_iterator begin() const; + + iterator end(); + const_iterator end() const; + + // vertex and normal wrap around so that i can never be out of range. + Vertex const& vertex(size_t i) const; + Vertex normal(size_t i) const; + + Vertex center() const; + + // a point in the volume, within min and max y, moved downwards to be a half + // width from the bottom (if that point is within a half width from the + // top, center() is returned) + Vertex bottomCenter() const; + + Rect boundBox() const; + + // Determine winding number of the given point. + int windingNumber(Vertex const& p) const; + + bool contains(Vertex const& p) const; + + // Normal SAT intersection finding the shortest separation of two convex + // polys. + IntersectResult satIntersection(Polygon const& p) const; + + // A directional version of a SAT intersection that will only separate + // parallel to the given direction. If choseSign is true, then the + // separation can occur either with the given direction or opposite it, but + // still parallel. If it is false, separation will always occur in the given + // direction only. + IntersectResult directionalSatIntersection(Polygon const& p, Vertex const& direction, bool chooseSign) const; + + // Returns the closest intersection with the poly, if any. + Maybe<LineIntersectResult> lineIntersection(Line const& l) const; + + bool intersects(Polygon const& p) const; + bool intersects(Line const& l) const; + +private: + // i must be between 0 and m_vertexes.size() - 1 + Line sideAt(size_t i) const; + + VertexList m_vertexes; +}; + +template <typename DataType> +std::ostream& operator<<(std::ostream& os, Polygon<DataType> const& poly); + +typedef Polygon<int> PolyI; +typedef Polygon<float> PolyF; +typedef Polygon<double> PolyD; + +template <typename DataType> +Polygon<DataType> Polygon<DataType>::convexHull(VertexList points) { + if (points.empty()) + return {}; + + auto cross = [](Vertex o, Vertex a, Vertex b) { + return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]); + }; + sort(points); + + VertexList lower; + for (auto const& point : points) { + while (lower.size() >= 2 && cross(lower[lower.size() - 2], lower[lower.size() - 1], point) <= 0) + lower.removeLast(); + lower.append(point); + } + + VertexList upper; + for (auto const& point : reverseIterate(points)) { + while (upper.size() >= 2 && cross(upper[upper.size() - 2], upper[upper.size() - 1], point) <= 0) + upper.removeLast(); + upper.append(point); + } + + upper.removeLast(); + lower.removeLast(); + lower.appendAll(take(upper)); + return Polygon<DataType>(move(lower)); +} + +template <typename DataType> +Polygon<DataType> Polygon<DataType>::clip(Polygon inputPoly, Polygon convexClipPoly) { + if (inputPoly.sides() == 0) + return inputPoly; + + auto insideEdge = [](Line const& edge, Vertex const& p) { + return ((edge.max() - edge.min()) ^ (p - edge.min())) > 0; + }; + + VertexList outputVertexes = take(inputPoly.m_vertexes); + for (size_t i = 0; i < convexClipPoly.sides(); ++i) { + if (outputVertexes.empty()) + break; + + Line clipEdge = convexClipPoly.sideAt(i); + VertexList inputVertexes = take(outputVertexes); + Vertex s = inputVertexes.last(); + for (Vertex e : inputVertexes) { + if (insideEdge(clipEdge, e)) { + if (!insideEdge(clipEdge, s)) + outputVertexes.append(clipEdge.intersection(Line(s, e)).point); + outputVertexes.append(e); + } else if (insideEdge(clipEdge, s)) { + outputVertexes.append(clipEdge.intersection(Line(s, e)).point); + } + s = e; + } + } + + return Polygon(move(outputVertexes)); +} + +template <typename DataType> +Polygon<DataType>::Polygon() {} + +template <typename DataType> +Polygon<DataType>::Polygon(Polygon const& rhs) + : m_vertexes(rhs.m_vertexes) {} + +template <typename DataType> +Polygon<DataType>::Polygon(Polygon&& rhs) + : m_vertexes(move(rhs.m_vertexes)) {} + +template <typename DataType> +template <typename DataType2> +Polygon<DataType>::Polygon(Box<DataType2, 2> const& rect) { + m_vertexes = { + Vertex(rect.min()), Vertex(rect.max()[0], rect.min()[1]), Vertex(rect.max()), Vertex(rect.min()[0], rect.max()[1])}; +} + +template <typename DataType> +template <typename DataType2> +Polygon<DataType>::Polygon(Polygon<DataType2> const& p2) { + for (auto const& v : p2) + m_vertexes.push_back(Vertex(v)); +} + +template <typename DataType> +Polygon<DataType>::Polygon(Vertex const& coord) { + m_vertexes.push_back(coord); +} + +template <typename DataType> +Polygon<DataType>::Polygon(VertexList const& vertexes) + : m_vertexes(vertexes) {} + +template <typename DataType> +Polygon<DataType>::Polygon(std::initializer_list<Vertex> vertexes) + : m_vertexes(vertexes) {} + +template <typename DataType> +bool Polygon<DataType>::isNull() const { + return m_vertexes.empty(); +} + +template <typename DataType> +bool Polygon<DataType>::isConvex() const { + if (sides() < 2) + return true; + + for (unsigned i = 0; i < sides(); ++i) { + if ((side(i + 1).diff() ^ side(i).diff()) > 0) + return false; + } + + return true; +} + +template <typename DataType> +float Polygon<DataType>::convexArea() const { + float area = 0.0f; + for (size_t i = 0; i < m_vertexes.size(); ++i) { + Vertex const& v1 = m_vertexes[i]; + Vertex const& v2 = i == m_vertexes.size() - 1 ? m_vertexes[0] : m_vertexes[i + 1]; + area += 0.5f * (v1[0] * v2[1] - v1[1] * v2[0]); + } + return area; +} + +template <typename DataType> +void Polygon<DataType>::deduplicateVertexes(float maxDistance) { + if (m_vertexes.empty()) + return; + + float distSquared = square(maxDistance); + VertexList newVertexes = {m_vertexes[0]}; + for (size_t i = 1; i < m_vertexes.size(); ++i) { + if (vmagSquared(m_vertexes[i] - newVertexes.last()) > distSquared) + newVertexes.append(m_vertexes[i]); + } + + if (vmagSquared(newVertexes.first() - newVertexes.last()) <= distSquared) + newVertexes.removeLast(); + + m_vertexes = move(newVertexes); +} + +template <typename DataType> +void Polygon<DataType>::add(Vertex const& a) { + m_vertexes.push_back(a); +} + +template <typename DataType> +void Polygon<DataType>::remove(size_t i) { + auto it = begin() + i % sides(); + m_vertexes.erase(it); +} + +template <typename DataType> +void Polygon<DataType>::clear() { + m_vertexes.clear(); +} + +template <typename DataType> +typename Polygon<DataType>::VertexList const& Polygon<DataType>::vertexes() const { + return m_vertexes; +} + +template <typename DataType> +typename Polygon<DataType>::VertexList& Polygon<DataType>::vertexes() { + return m_vertexes; +} + +template <typename DataType> +size_t Polygon<DataType>::sides() const { + return m_vertexes.size(); +} + +template <typename DataType> +typename Polygon<DataType>::Line Polygon<DataType>::side(size_t i) const { + return sideAt(i % m_vertexes.size()); +} + +template <typename DataType> +DataType Polygon<DataType>::distance(Vertex const& c) const { + if (contains(c)) + return 0; + + DataType dist = highest<DataType>(); + for (size_t i = 0; i < m_vertexes.size(); ++i) + dist = min(dist, sideAt(i).distanceTo(c)); + + return dist; +} + +template <typename DataType> +void Polygon<DataType>::translate(Vertex const& c) { + for (auto& v : m_vertexes) + v += c; +} + +template <typename DataType> +void Polygon<DataType>::setCenter(Vertex const& c) { + translate(c - center()); +} + +template <typename DataType> +void Polygon<DataType>::rotate(DataType a, Vertex const& c) { + for (auto& v : m_vertexes) + v = (v - c).rotate(a) + c; +} + +template <typename DataType> +void Polygon<DataType>::scale(Vertex const& s, Vertex const& c) { + for (auto& v : m_vertexes) + v = vmult((v - c), s) + c; +} + +template <typename DataType> +void Polygon<DataType>::scale(DataType s, Vertex const& c) { + scale(Vertex::filled(s), c); +} + +template <typename DataType> +void Polygon<DataType>::flipHorizontal(DataType horizontalPos) { + scale(Vertex(-1, 1), Vertex(horizontalPos, 0)); + // Reverse vertexes to make sure poly remains counter-clockwise after flip. + std::reverse(m_vertexes.begin(), m_vertexes.end()); +} + +template <typename DataType> +void Polygon<DataType>::flipVertical(DataType verticalPos) { + scale(Vertex(1, -1), Vertex(0, verticalPos)); + // Reverse vertexes to make sure poly remains counter-clockwise after flip. + std::reverse(m_vertexes.begin(), m_vertexes.end()); +} + +template <typename DataType> +template <typename DataType2> +void Polygon<DataType>::transform(Matrix3<DataType2> const& transMat) { + for (auto& v : m_vertexes) + v = transMat.transformVec2(v); +} + +template <typename DataType> +typename Polygon<DataType>::Vertex const& Polygon<DataType>::operator[](size_t i) const { + return m_vertexes[i]; +} + +template <typename DataType> +typename Polygon<DataType>::Vertex& Polygon<DataType>::operator[](size_t i) { + return m_vertexes[i]; +} + +template <typename DataType> +bool Polygon<DataType>::operator==(Polygon<DataType> const& rhs) const { + return m_vertexes == rhs.m_vertexes; +} + +template <typename DataType> +Polygon<DataType>& Polygon<DataType>::operator=(Polygon const& rhs) { + m_vertexes = rhs.m_vertexes; + return *this; +} + +template <typename DataType> +Polygon<DataType>& Polygon<DataType>::operator=(Polygon&& rhs) { + m_vertexes = move(rhs.m_vertexes); + return *this; +} + +template <typename DataType> +typename Polygon<DataType>::iterator Polygon<DataType>::begin() { + return m_vertexes.begin(); +} + +template <typename DataType> +typename Polygon<DataType>::const_iterator Polygon<DataType>::begin() const { + return m_vertexes.begin(); +} + +template <typename DataType> +typename Polygon<DataType>::iterator Polygon<DataType>::end() { + return m_vertexes.end(); +} + +template <typename DataType> +typename Polygon<DataType>::const_iterator Polygon<DataType>::end() const { + return m_vertexes.end(); +} + +template <typename DataType> +typename Polygon<DataType>::Vertex const& Polygon<DataType>::vertex(size_t i) const { + return m_vertexes[i % m_vertexes.size()]; +} + +template <typename DataType> +typename Polygon<DataType>::Vertex Polygon<DataType>::normal(size_t i) const { + Vertex diff = side(i).diff(); + + if (diff == Vertex()) + return Vertex(); + + return diff.rot90().normalized(); +} + +template <typename DataType> +typename Polygon<DataType>::Vertex Polygon<DataType>::center() const { + return std::accumulate(m_vertexes.begin(), m_vertexes.end(), Vertex()) / (DataType)m_vertexes.size(); +} + +template <typename DataType> +typename Polygon<DataType>::Vertex Polygon<DataType>::bottomCenter() const { + if (m_vertexes.size() == 0) + return Vertex(); + Polygon<DataType>::Vertex center = std::accumulate(m_vertexes.begin(), m_vertexes.end(), Vertex()) / (DataType)m_vertexes.size(); + Polygon<DataType>::Vertex bottomLeft = *std::min_element(m_vertexes.begin(), m_vertexes.end()); + Polygon<DataType>::Vertex topRight = *std::max_element(m_vertexes.begin(), m_vertexes.end()); + Polygon<DataType>::Vertex size = topRight - bottomLeft; + if (size.x() > size.y()) + return center; + return Polygon<DataType>::Vertex(center.x(), bottomLeft.y() + size.x() / 2.0f); +} + +template <typename DataType> +auto Polygon<DataType>::boundBox() const -> Rect { + auto bounds = Rect::null(); + for (auto const& v : m_vertexes) + bounds.combine(v); + return bounds; +} + +template <typename DataType> +int Polygon<DataType>::windingNumber(Vertex const& p) const { + + auto isLeft = [](Vertex const& p0, Vertex const& p1, Vertex const& p2) { + return ((p1[0] - p0[0]) * (p2[1] - p0[1]) - (p2[0] - p0[0]) * (p1[1] - p0[1])); + }; + + // the winding number counter + int wn = 0; + + // loop through all edges of the polygon + for (size_t i = 0; i < m_vertexes.size(); ++i) { + auto const& first = m_vertexes[i]; + auto const& second = i == m_vertexes.size() - 1 ? m_vertexes[0] : m_vertexes[i + 1]; + + // start y <= p[1] + if (first[1] <= p[1]) { + if (second[1] > p[1]) { + // an upward crossing + if (isLeft(first, second, p) > 0) { + // p left of edge + // have a valid up intersect + ++wn; + } + } + } else { + // start y > p[1] (no test needed) + if (second[1] <= p[1]) { + // a downward crossing + if (isLeft(first, second, p) < 0) { + // p right of edge + // have a valid down intersect + --wn; + } + } + } + } + + return wn; +} + +template <typename DataType> +bool Polygon<DataType>::contains(Vertex const& p) const { + return windingNumber(p) != 0; +} + +template <typename DataType> +typename Polygon<DataType>::IntersectResult Polygon<DataType>::satIntersection(Polygon const& p) const { + // "Accumulates" the shortest separating distance and axis of this poly and + // the given poly, after projecting all the vertexes of each poly onto a + // given axis. Used by SAT intersection, meant to be called with each tested + // axis. + auto accumSeparator = [this](Polygon const& p, Vertex const& axis, DataType& shortestOverlap, Vertex& finalSepDir) { + DataType myProjectionLow = std::numeric_limits<DataType>::max(); + DataType targetProjectionHigh = std::numeric_limits<DataType>::lowest(); + + for (auto const& v : m_vertexes) { + DataType p = axis[0] * v[0] + axis[1] * v[1]; + if (p < myProjectionLow) + myProjectionLow = p; + } + + for (auto const& v : p.m_vertexes) { + DataType p = axis[0] * v[0] + axis[1] * v[1]; + if (p > targetProjectionHigh) + targetProjectionHigh = p; + } + + float overlap = targetProjectionHigh - myProjectionLow; + if (overlap < shortestOverlap) { + shortestOverlap = overlap; + finalSepDir = axis; + } + }; + + DataType overlap = std::numeric_limits<DataType>::max(); + Vertex separatingDir = Vertex(); + + if (!m_vertexes.empty()) { + Vertex pv = m_vertexes[m_vertexes.size() - 1]; + for (auto const& v : m_vertexes) { + Vertex sideNormal = pv - v; + if (sideNormal != Vertex()) { + sideNormal = sideNormal.rot90().normalized(); + accumSeparator(p, -sideNormal, overlap, separatingDir); + } + pv = v; + } + } + + if (!p.m_vertexes.empty()) { + Vertex pv = p.m_vertexes[p.m_vertexes.size() - 1]; + for (auto const& v : p.m_vertexes) { + Vertex sideNormal = pv - v; + if (sideNormal != Vertex()) { + sideNormal = sideNormal.rot90().normalized(); + accumSeparator(p, sideNormal, overlap, separatingDir); + } + pv = v; + } + } + + IntersectResult isect; + isect.intersects = (overlap > 0); + isect.overlap = separatingDir * overlap; + + return isect; +} + +template <typename DataType> +typename Polygon<DataType>::IntersectResult Polygon<DataType>::directionalSatIntersection( + Polygon const& p, Vertex const& direction, bool chooseSign) const { + // A "directional" version of accumSeparator, that when intersecting only + // ever tries to separate in the given direction. + auto directionalAccumSeparator = [this](Polygon const& p, Vertex axis, DataType& shortestOverlap, + Vertex const& separatingDir, Vertex& finalSepDir, bool chooseDir) { + DataType myProjectionLow = std::numeric_limits<DataType>::max(); + DataType targetProjectionHigh = std::numeric_limits<DataType>::lowest(); + + for (auto const& v : m_vertexes) { + DataType p = axis[0] * v[0] + axis[1] * v[1]; + if (p < myProjectionLow) + myProjectionLow = p; + } + + for (auto const& v : p.m_vertexes) { + DataType p = axis[0] * v[0] + axis[1] * v[1]; + if (p > targetProjectionHigh) + targetProjectionHigh = p; + } + + float overlap = targetProjectionHigh - myProjectionLow; + + // Separation was found, skip the rest of the method. + if (overlap <= 0) { + if (overlap < shortestOverlap) { + shortestOverlap = overlap; + finalSepDir = axis; + } + return; + } + + DataType axisDot = separatingDir * axis; + + // Now, if we don't have separation and the axis is perpendicular to + // requested, we can do nothing, return. + if (axisDot == 0) + return; + + // Separate along the given separating direction enough to separate as + // determined by this axis. + DataType projOverlap = overlap / axisDot; + if (chooseDir) { + DataType absProjOverlap = (projOverlap >= 0) ? projOverlap : -projOverlap; + if (absProjOverlap < shortestOverlap) { + shortestOverlap = absProjOverlap; + finalSepDir = separatingDir * (projOverlap / absProjOverlap); + } + } else if (projOverlap >= 0) { + if (projOverlap < shortestOverlap) { + shortestOverlap = projOverlap; + finalSepDir = separatingDir; + } + } + }; + + DataType overlap = std::numeric_limits<DataType>::max(); + Vertex separatingDir = Vertex(); + + if (!m_vertexes.empty()) { + Vertex pv = m_vertexes[m_vertexes.size() - 1]; + for (auto const& v : m_vertexes) { + Vertex sideNormal = pv - v; + if (sideNormal != Vertex()) { + sideNormal = sideNormal.rot90().normalized(); + directionalAccumSeparator(p, -sideNormal, overlap, direction, separatingDir, chooseSign); + } + pv = v; + } + } + + if (!p.m_vertexes.empty()) { + Vertex pv = p.m_vertexes[p.m_vertexes.size() - 1]; + for (auto const& v : p.m_vertexes) { + Vertex sideNormal = pv - v; + if (sideNormal != Vertex()) { + sideNormal = sideNormal.rot90().normalized(); + directionalAccumSeparator(p, sideNormal, overlap, direction, separatingDir, chooseSign); + } + pv = v; + } + } + + IntersectResult isect; + isect.intersects = (overlap > 0); + isect.overlap = separatingDir * overlap; + + return isect; +} + +template <typename DataType> +auto Polygon<DataType>::lineIntersection(Line const& l) const -> Maybe<LineIntersectResult> { + if (contains(l.min())) + return LineIntersectResult{l.min(), DataType(0), {}}; + + Maybe<LineIntersectResult> nearestIntersection; + for (size_t i = 0; i < m_vertexes.size(); ++i) { + auto intersection = l.intersection(sideAt(i)); + if (intersection.intersects) { + if (!nearestIntersection || intersection.t < nearestIntersection->along) + nearestIntersection = LineIntersectResult{intersection.point, intersection.t, i}; + } + } + return nearestIntersection; +} + +template <typename DataType> +bool Polygon<DataType>::intersects(Polygon const& p) const { + return satIntersection(p).intersects; +} + +template <typename DataType> +bool Polygon<DataType>::intersects(Line const& l) const { + if (contains(l.min()) || contains(l.max())) + return true; + + for (size_t i = 0; i < m_vertexes.size(); ++i) { + if (l.intersects(sideAt(i))) + return true; + } + + return false; +} + +template <typename DataType> +auto Polygon<DataType>::sideAt(size_t i) const -> Line { + if (i == m_vertexes.size() - 1) + return Line(m_vertexes[i], m_vertexes[0]); + else + return Line(m_vertexes[i], m_vertexes[i + 1]); +} + +template <typename DataType> +std::ostream& operator<<(std::ostream& os, Polygon<DataType> const& poly) { + os << "[Poly: "; + for (auto i = poly.begin(); i != poly.end(); ++i) { + if (i != poly.begin()) + os << ", "; + os << *i; + } + os << "]"; + return os; +} + +} + +#endif |