| #ifndef _RIVE_RECTANGLES_TO_CONTOUR_HPP_ |
| #define _RIVE_RECTANGLES_TO_CONTOUR_HPP_ |
| #include <vector> |
| #include <unordered_set> |
| |
| #include "rive/math/mat2d.hpp" |
| #include "rive/math/aabb.hpp" |
| |
| #ifdef TESTING |
| // When building for testing we use an ordered map for the EdgeMap so we can get |
| // crossplatform stable order of contours. Visually this doesn't matter but for |
| // testing across results across different platforms, it does. |
| #include <map> |
| struct EdgeMapTesting |
| { |
| bool operator()(const rive::Vec2D& a, const rive::Vec2D& b) const |
| { |
| return a.x < b.x || (a.x == b.x && a.y < b.y); |
| } |
| }; |
| using EdgeMap = std::map<rive::Vec2D, rive::Vec2D, EdgeMapTesting>; |
| #else |
| #include <unordered_map> |
| using EdgeMap = std::unordered_map<rive::Vec2D, rive::Vec2D>; |
| #endif |
| |
| namespace rive |
| { |
| struct ContourPoint |
| { |
| Vec2D vec; |
| int dir; // 0 for horizontal, 1 for vertical |
| |
| ContourPoint(const Vec2D& vec, int dir) : vec(vec), dir(dir) {} |
| |
| bool operator==(const ContourPoint& other) const |
| { |
| return vec == other.vec && dir == other.dir; |
| } |
| }; |
| |
| class RectanglesToContour; |
| class Contour; |
| |
| // A helper for iterating the points in a contour, lazily skipping points which |
| // have the same value. |
| class ContourPointItr |
| { |
| public: |
| ContourPointItr() = default; |
| ContourPointItr(const Span<const ContourPoint> contour, size_t pointIndex) : |
| m_contour(contour), m_pointIndex(pointIndex) |
| {} |
| |
| bool operator!=(const ContourPointItr& that) const |
| { |
| return m_pointIndex != that.m_pointIndex || m_contour != that.m_contour; |
| } |
| bool operator==(const ContourPointItr& that) const |
| { |
| return m_pointIndex == that.m_pointIndex && m_contour == that.m_contour; |
| } |
| |
| ContourPointItr& operator++(); |
| |
| Vec2D operator*() const; |
| |
| private: |
| const Span<const ContourPoint> m_contour; |
| size_t m_pointIndex; |
| }; |
| |
| class Contour |
| { |
| public: |
| Contour(Span<const ContourPoint> points) : m_points(points) {} |
| ContourPointItr begin() const { return ContourPointItr(m_points, 0); } |
| ContourPointItr end() const |
| { |
| return ContourPointItr(m_points, m_points.size()); |
| } |
| |
| size_t size() const { return m_points.size(); } |
| Vec2D point(size_t index) const { return m_points[index].vec; } |
| Vec2D point(size_t index, bool reversed) const |
| { |
| if (reversed) |
| { |
| return m_points[m_points.size() - 1 - index].vec; |
| } |
| return m_points[index].vec; |
| } |
| bool isClockwise() const; |
| |
| private: |
| Span<const ContourPoint> m_points; |
| }; |
| |
| // A helper for iterating the contours computed by RectanglesToContour. This |
| // allows RectanglesToContour to store a linear array of contour points for all |
| // contours. |
| class ContourItr |
| { |
| public: |
| ContourItr() = default; |
| ContourItr(const RectanglesToContour* rectanglesToContour, |
| size_t contourIndex) : |
| m_rectanglesToContour(rectanglesToContour), m_contourIndex(contourIndex) |
| {} |
| |
| bool operator!=(const ContourItr& that) const |
| { |
| return m_contourIndex != that.m_contourIndex || |
| m_rectanglesToContour != that.m_rectanglesToContour; |
| } |
| bool operator==(const ContourItr& that) const |
| { |
| return m_contourIndex == that.m_contourIndex && |
| m_rectanglesToContour == that.m_rectanglesToContour; |
| } |
| |
| ContourItr& operator++(); |
| |
| Contour operator*() const; |
| |
| private: |
| const RectanglesToContour* m_rectanglesToContour; |
| size_t m_contourIndex; |
| }; |
| |
| class RectanglesToContour |
| { |
| public: |
| // Add a rectangle to the contour computation. |
| void addRect(const AABB& rect); |
| // Compute the contours once all rects have been added. |
| void computeContours(); |
| // Reset everything to start adding rects again. |
| void reset(); |
| |
| // Results can be queried via the utilities below. |
| // |
| // For example you can iterate the result: |
| // for (auto contour : converter) |
| // { |
| // for (auto point : contour) |
| // { |
| // printf("contour point: %f %f\n", point.x, point.y); |
| // } |
| // } |
| size_t contourCount() const { return m_contourOffsets.size(); } |
| Contour contour(size_t index) const; |
| |
| ContourItr begin() const { return ContourItr(this, 0); } |
| ContourItr end() const { return ContourItr(this, m_contourOffsets.size()); } |
| |
| struct RectEvent |
| { |
| size_t index = 0; |
| float size = 0; |
| uint8_t type = 0; |
| float x = 0; |
| float y = 0; |
| |
| float getValue(uint8_t axis) const { return axis == 0 ? x : y; } |
| }; |
| |
| private: |
| // These arrays and maps are built up accumulating temporary data used to |
| // build the contours from the rectangles. We store them on the |
| // RectanglesToContour class to leverage re-using their reserved memory on |
| // re-runs. For this reason it's encouraged to keep the RectanglesToContour |
| // around when you know you'll need to recompute the contour from a set of |
| // rectangles often/in rapid succession. |
| std::vector<RectEvent> m_rectEvents; |
| EdgeMap m_edgesH; |
| EdgeMap m_edgesV; |
| std::unordered_set<Vec2D> m_uniquePoints; |
| std::vector<AABB> m_rects; |
| std::vector<AABB> m_subdividedRects; |
| std::vector<uint8_t> m_rectInclusionBitset; |
| std::vector<Vec2D> m_sortedPointsX; |
| std::vector<Vec2D> m_sortedPointsY; |
| |
| // The entire set of contour points where each contour is tightly packed |
| // after the previous at offsets defined in m_contourOffsets. |
| std::vector<ContourPoint> m_contourPoints; |
| std::vector<size_t> m_contourOffsets; |
| |
| bool isRectIncluded(size_t rectIndex); |
| void markRectIncluded(size_t rectIndex, bool isIt); |
| |
| void addUniquePoint(const Vec2D& point); |
| void subdivideRectangles(); |
| }; |
| } // namespace rive |
| #endif |