blob: 6b0c928a6d1b5cf4a551e34b981c103357641df0 [file] [log] [blame]
#include "rive/math/rectangles_to_contour.hpp"
#include <algorithm>
#include <unordered_map>
using namespace rive;
struct RectEvent
{
int index = 0;
float size = 0;
int type = 0;
float x = 0;
float y = 0;
float getValue(int axis) const { return axis == 0 ? x : y; }
};
std::vector<Rect> subdivideRectangles(const std::vector<Rect>& rects)
{
if (rects.empty())
{
return rects;
}
std::vector<std::vector<std::vector<float>>> vrects;
for (const auto& rect : rects)
{
vrects.push_back({{rect.left, rect.top}, {rect.right, rect.bottom}});
}
auto sortEvents = [&](int axisA, int axisB) -> std::vector<RectEvent> {
std::vector<RectEvent> result;
int index = 0;
for (const auto& rect : vrects)
{
int mindex = 0;
for (const auto& e : rect)
{
RectEvent event;
event.type = mindex;
event.index = index;
event.y = axisB == 0 ? e[axisA] : e[axisB];
event.x = axisB == 0 ? e[axisB] : e[axisA];
event.size = mindex++ == 0 ? rect[1][axisB] - e[axisB]
: e[axisB] - rect[0][axisB];
result.push_back(event);
}
++index;
}
std::sort(result.begin(),
result.end(),
[&](const RectEvent& a, const RectEvent& b) {
return a.getValue(axisB) < b.getValue(axisB);
});
std::sort(result.begin(),
result.end(),
[&](const RectEvent& a, const RectEvent& b) {
return a.getValue(axisA) < b.getValue(axisA);
});
return result;
};
auto ev = sortEvents(0, 1);
auto eh = sortEvents(1, 0);
std::vector<bool> included(vrects.size(), false);
included[ev[0].index] = true;
int opened = 0;
float beginY = 0;
std::vector<Rect> result;
for (size_t i = 0; i < ev.size() - 1; ++i)
{
const auto& eventV = ev[i];
included[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 (included[eventH.index])
{
if (eventH.type == 0)
{
++opened;
if (opened == 1)
{
beginY = eventH.y;
}
}
else
{
--opened;
size_t n = 1;
while (j + n < eh.size() && !included[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;
result.push_back(
Rect::fromLTRB(beginX, beginY, endX, endY));
}
}
}
}
}
return result;
}
void RectanglesToContour::addRect(const Rect& rect)
{
if (!rects.empty())
{
auto& last = rects.back();
if (last.top == rect.top && last.bottom == rect.bottom &&
last.right == rect.left)
{
rects.pop_back();
rects.emplace_back(
Rect::fromLTRB(last.left, last.top, rect.right, last.bottom));
return;
}
}
rects.push_back(rect);
}
template <typename Compare>
std::vector<Vec2D> sortPoints(const std::unordered_set<Vec2D>& points,
Compare comp)
{
std::vector<Vec2D> sortedPoints(points.begin(), points.end());
std::sort(sortedPoints.begin(), sortedPoints.end(), comp);
return sortedPoints;
}
std::vector<std::vector<Vec2D>> extractPolygons(
std::unordered_map<Vec2D, Vec2D>& edgesH,
std::unordered_map<Vec2D, Vec2D>& edgesV)
{
std::vector<std::vector<Vec2D>> polygons;
while (!edgesH.empty())
{
Vec2D start = edgesH.begin()->first;
edgesH.erase(start);
std::vector<PolygonPoint> polygon = {PolygonPoint(start, 0)};
while (true)
{
auto& curr = polygon.back();
if (curr.dir == 0)
{
if (edgesV.find(curr.vec) != edgesV.end())
{
auto next = edgesV[curr.vec];
edgesV.erase(curr.vec);
polygon.emplace_back(next, 1);
}
else
{
break;
}
}
else
{
if (edgesH.find(curr.vec) != edgesH.end())
{
auto next = edgesH[curr.vec];
edgesH.erase(curr.vec);
polygon.emplace_back(next, 0);
}
else
{
break;
}
}
if (polygon.front() == polygon.back())
{
polygon.pop_back();
break;
}
}
std::vector<Vec2D> poly;
std::transform(polygon.begin(),
polygon.end(),
std::back_inserter(poly),
[](const PolygonPoint& pp) { return pp.vec; });
polygons.push_back(poly);
for (const Vec2D& vertex : poly)
{
edgesH.erase(vertex);
edgesV.erase(vertex);
}
}
return polygons;
}
std::vector<std::vector<Vec2D>> RectanglesToContour::computeContours()
{
auto subdividedRects = subdivideRectangles(rects);
for (const auto& rect : subdividedRects)
{
addUniquePoint(Vec2D(rect.left, rect.top));
addUniquePoint(Vec2D(rect.right, rect.top));
addUniquePoint(Vec2D(rect.right, rect.bottom));
addUniquePoint(Vec2D(rect.left, rect.bottom));
}
auto sortX = sortPoints(uniquePoints, [](const Vec2D& a, const Vec2D& b) {
return a.x < b.x || (a.x == b.x && a.y < b.y);
});
auto sortY = sortPoints(uniquePoints, [](const Vec2D& a, const Vec2D& b) {
return a.y < b.y || (a.y == b.y && a.x < b.x);
});
std::unordered_map<Vec2D, Vec2D> edgesH, edgesV;
size_t i = 0;
while (i < sortY.size())
{
float currY = sortY[i].y;
while (sortY[i].y == currY && i < sortY.size())
{
edgesH[sortY[i]] = sortY[i + 1];
edgesH[sortY[i + 1]] = sortY[i];
i += 2;
}
}
i = 0;
while (i < sortX.size())
{
float currX = sortX[i].x;
while (sortX[i].x == currX && i < sortX.size())
{
edgesV[sortX[i]] = sortX[i + 1];
edgesV[sortX[i + 1]] = sortX[i];
i += 2;
}
}
return extractPolygons(edgesH, edgesV);
}
void RectanglesToContour::addUniquePoint(const Vec2D& point)
{
if (!uniquePoints.insert(point).second)
{
uniquePoints.erase(point);
}
}
std::vector<std::vector<Vec2D>> RectanglesToContour::makeSelectionContours(
const std::vector<Rect>& rects)
{
RectanglesToContour converter;
// Add rectangles to the converter
for (const auto& rect : rects)
{
converter.addRect(rect);
}
std::vector<std::vector<Vec2D>> renderContours;
auto contours = converter.computeContours();
// Process the contours to create renderContours
for (const auto& contour : contours)
{
if (contour.empty())
{
continue;
}
std::vector<Vec2D> renderContour = {contour.front()};
for (auto it = std::next(contour.begin()); it != contour.end(); ++it)
{
if (renderContour.back() == *it)
{
continue;
}
renderContour.push_back(*it);
}
renderContours.push_back(renderContour);
}
return renderContours;
}