blob: 7ac4cdf1ae7a488e2932a475db52289a4f5b6155 [file] [log] [blame]
#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