blob: ba09e6465cc7f3f92bb887ea04e2bfec7cb70ee1 [file] [log] [blame] [edit]
#include "rive/math/rectangles_to_contour.hpp"
#include <algorithm>
using namespace rive;
using RectEvent = RectanglesToContour::RectEvent;
class SpanOffset
{
public:
size_t start;
size_t size;
Span<RectEvent> toSpan(Span<RectEvent> base)
{
return Span<RectEvent>(base.data() + start, size);
}
};
// Returns SpanOffset relative to result so it can be called in succession
// (re-allocating) prior to accessing data.
static SpanOffset sortRectEvents(const std::vector<AABB>& rects,
std::vector<RectEvent>& result,
uint8_t axisA,
uint8_t axisB)
{
size_t index = 0;
size_t resultStart = result.size();
for (const AABB& rect : rects)
{
for (size_t pointIndex = 0; pointIndex < 2; pointIndex++)
{
Vec2D e = rect[pointIndex];
RectEvent event;
event.type = (uint8_t)pointIndex;
event.index = index;
event.y = axisB == 0 ? e[axisA] : e[axisB];
event.x = axisB == 0 ? e[axisB] : e[axisA];
event.size = pointIndex == 0 ? rect[1][axisB] - e[axisB]
: e[axisB] - rect[0][axisB];
result.push_back(event);
}
++index;
}
std::sort(result.begin() + resultStart,
result.end(),
[&](const RectEvent& a, const RectEvent& b) {
return a.getValue(axisB) < b.getValue(axisB);
});
std::sort(result.begin() + resultStart,
result.end(),
[&](const RectEvent& a, const RectEvent& b) {
return a.getValue(axisA) < b.getValue(axisA);
});
return {resultStart, result.size() - resultStart};
}
bool RectanglesToContour::isRectIncluded(size_t rectIndex)
{
return (m_rectInclusionBitset[rectIndex / 8] & (1 << (rectIndex % 8))) != 0;
}
void RectanglesToContour::markRectIncluded(size_t rectIndex, bool isIt)
{
if (isIt)
{
m_rectInclusionBitset[rectIndex / 8] |= 1 << (rectIndex % 8);
}
else
{
m_rectInclusionBitset[rectIndex / 8] &= ~(1 << (rectIndex % 8));
}
}
void RectanglesToContour::subdivideRectangles()
{
m_subdividedRects.clear();
m_uniquePoints.clear();
m_rectEvents.clear();
if (m_rects.empty())
{
return;
}
auto evOffset = sortRectEvents(m_rects, m_rectEvents, 0, 1);
auto ehOffset = sortRectEvents(m_rects, m_rectEvents, 1, 0);
auto ev = evOffset.toSpan(m_rectEvents);
auto eh = ehOffset.toSpan(m_rectEvents);
size_t inclusionByteSize = m_rects.size() / 8 + 1;
m_rectInclusionBitset.resize(inclusionByteSize);
memset(m_rectInclusionBitset.data(), 0, inclusionByteSize);
markRectIncluded(ev[0].index, true);
int opened = 0;
float beginY = 0;
std::vector<AABB> result;
for (size_t i = 0; i < ev.size() - 1; ++i)
{
const auto& eventV = ev[i];
markRectIncluded(eventV.index, eventV.type == 0);
const auto& next = ev[i + 1];
float beginX = eventV.x;
float endX = next.x;
float deltaX = next.x - eventV.x;
if (deltaX == 0)
{
continue;
}
for (size_t j = 0; j < eh.size(); ++j)
{
const auto& eventH = eh[j];
if (isRectIncluded(eventH.index))
{
if (eventH.type == 0)
{
++opened;
if (opened == 1)
{
beginY = eventH.y;
}
}
else
{
--opened;
size_t n = 1;
while (j + n < eh.size() &&
!isRectIncluded(eh[j + n].index))
{
++n;
}
const auto* next =
(j + n < eh.size()) ? &eh[j + n] : nullptr;
if (!next || (opened == 0 && next->y != eventH.y))
{
float endY = eventH.y;
m_subdividedRects.push_back(
AABB(beginX, beginY, endX, endY));
}
}
}
}
}
}
void RectanglesToContour::reset() { m_rects.clear(); }
void RectanglesToContour::addRect(const AABB& rect)
{
if (!m_rects.empty())
{
auto& last = m_rects.back();
if (last.minY == rect.minY && last.maxY == rect.maxY &&
last.maxX == rect.minX)
{
m_rects.pop_back();
m_rects.emplace_back(
AABB(last.minX, last.minY, rect.maxX, last.maxY));
return;
}
}
m_rects.push_back(rect);
}
// Build contours and append them into contourPoints delineated by
// contourOffsets.
void extractPolygons(std::vector<ContourPoint>& contourPoints,
std::vector<size_t>& contourOffsets,
EdgeMap& edgesH,
EdgeMap& edgesV)
{
while (!edgesH.empty())
{
size_t contourStartOffset = contourPoints.size();
Vec2D start = edgesH.begin()->first;
edgesH.erase(start);
auto first = ContourPoint(start, 0);
contourPoints.push_back(first);
while (true)
{
auto& curr = contourPoints.back();
if (curr.dir == 0)
{
if (edgesV.find(curr.vec) != edgesV.end())
{
auto next = edgesV[curr.vec];
edgesV.erase(curr.vec);
contourPoints.emplace_back(next, 1);
}
else
{
break;
}
}
else
{
if (edgesH.find(curr.vec) != edgesH.end())
{
auto next = edgesH[curr.vec];
edgesH.erase(curr.vec);
contourPoints.emplace_back(next, 0);
}
else
{
break;
}
}
// Remove the last point if the first and last in the contour are
// the same.
if (first == contourPoints.back())
{
contourPoints.pop_back();
break;
}
}
contourOffsets.push_back(contourPoints.size());
for (size_t index = contourStartOffset; index < contourPoints.size();
index++)
{
Vec2D vertex = contourPoints[index].vec;
edgesH.erase(vertex);
edgesV.erase(vertex);
}
}
}
void RectanglesToContour::computeContours()
{
subdivideRectangles();
for (const auto& rect : m_subdividedRects)
{
addUniquePoint(Vec2D(rect.minX, rect.minY));
addUniquePoint(Vec2D(rect.maxX, rect.minY));
addUniquePoint(Vec2D(rect.maxX, rect.maxY));
addUniquePoint(Vec2D(rect.minX, rect.maxY));
}
m_sortedPointsX.clear();
m_sortedPointsY.clear();
for (auto pt : m_uniquePoints)
{
m_sortedPointsX.push_back(pt);
m_sortedPointsY.push_back(pt);
}
std::sort(m_sortedPointsX.begin(),
m_sortedPointsX.end(),
[](const Vec2D& a, const Vec2D& b) {
return a.x < b.x || (a.x == b.x && a.y < b.y);
});
std::sort(m_sortedPointsY.begin(),
m_sortedPointsY.end(),
[](const Vec2D& a, const Vec2D& b) {
return a.y < b.y || (a.y == b.y && a.x < b.x);
});
// std::unordered_map isn't guaranteed to reserve memory, so this clear
// is more in prep for when we allow using allocators. We could also
// experiment with a simple linear search in a vector as usually there
// are not a lot of edges to traverse.
m_edgesH.clear();
m_edgesV.clear();
size_t i = 0;
while (i < m_sortedPointsY.size())
{
float currY = m_sortedPointsY[i].y;
while (i < m_sortedPointsY.size() && m_sortedPointsY[i].y == currY)
{
m_edgesH[m_sortedPointsY[i]] = m_sortedPointsY[i + 1];
m_edgesH[m_sortedPointsY[i + 1]] = m_sortedPointsY[i];
i += 2;
}
}
i = 0;
while (i < m_sortedPointsX.size())
{
float currX = m_sortedPointsX[i].x;
while (i < m_sortedPointsX.size() && m_sortedPointsX[i].x == currX)
{
m_edgesV[m_sortedPointsX[i]] = m_sortedPointsX[i + 1];
m_edgesV[m_sortedPointsX[i + 1]] = m_sortedPointsX[i];
i += 2;
}
}
m_contourPoints.clear();
m_contourOffsets.clear();
extractPolygons(m_contourPoints, m_contourOffsets, m_edgesH, m_edgesV);
}
void RectanglesToContour::addUniquePoint(const Vec2D& point)
{
if (!m_uniquePoints.insert(point).second)
{
m_uniquePoints.erase(point);
}
}
ContourItr& ContourItr::operator++()
{
m_contourIndex++;
return *this;
}
Contour ContourItr::operator*() const
{
return m_rectanglesToContour->contour(m_contourIndex);
}
Contour RectanglesToContour::contour(size_t index) const
{
assert(index < m_contourOffsets.size());
size_t end = m_contourOffsets[index];
size_t start = index == 0 ? 0 : m_contourOffsets[index - 1];
const ContourPoint* data = m_contourPoints.data() + start;
size_t size = end - start;
return Contour(Span<const ContourPoint>(data, size));
}
ContourPointItr& ContourPointItr::operator++()
{
Vec2D currentPoint = m_pointIndex < m_contour.size() ? *(*this) : Vec2D();
m_pointIndex++;
while (m_pointIndex < m_contour.size())
{
auto nextPoint = m_contour[m_pointIndex].vec;
if (nextPoint != currentPoint)
{
break;
}
m_pointIndex++;
}
return *this;
}
Vec2D ContourPointItr::operator*() const { return m_contour[m_pointIndex].vec; }